一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Java IO 流:字节流:字符流与 try-with-resources 的正确打开方式

时间:2026-06-03 12:05:01 编辑:袖梨 来源:一聚教程网

文件读写作为后端开发的基础,Java IO体系的设计堪称经典。本文围绕字节流与字符流的区别展开,系统讲解它们的分类、操作方式及资源管理的最佳实践。

前言

学习IO流时,许多开发者容易混淆字节流和字符流。文件读写是后端开发的基础操作,而Java的IO体系设计虽然经典,但初次接触时理解两类流的差异至关重要。

Java IO 流:字节流、字符流与 try-with-resources 的正确打开方式


1. IO 流分类

按方向划分:输入流(读取数据)与输出流(写入数据)
按单位划分:字节流(处理二进制)与字符流(处理文本)
按功能划分:节点流与处理流(如缓冲、转换等)
场景分类指南:
第一类,适用于图片、视频、可执行文件:选择字节流,原因在于二进制数据无需解释内容。
第二类,适用于文本、配置文件、日志:选择字符流,原因在于自动处理字符编码。
第三类,适用于大文件拷贝:选择缓冲流,原因在于减少系统调用次数。

2. 字节流:文件拷贝

// 逐字节拷贝方式,速度较慢
FileInputStream fis = new FileInputStream("src.jpg");
FileOutputStream fos = new FileOutputStream("dest.jpg");
int b;
while ((b = fis.read()) != -1) {
    fos.write(b);
}
// 缓冲区拷贝方式,速度提升约100倍
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dest.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
    bos.write(buffer, 0, len);
}

3. 字符流:文本处理

// 写入文本操作
PrintWriter pw = new PrintWriter(new FileWriter("log.txt", true));
pw.println("日志内容");
pw.printf("用户:%s,操作:%s%n", "张三", "登录");
// 读取文本操作,最常用的方式
BufferedReader br = new BufferedReader(new FileReader("log.txt"));
String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}

4. try-with-resources:自动关闭的优雅

// 传统写法需在finally中手动关闭流,代码较为膨胀
// 现代写法在try结束时自动关闭资源
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    return br.readLine();
}

前提条件:资源必须实现AutoCloseable接口。所有IO流均已实现该接口。

5. 序列化:对象的持久化

// 序列化操作
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"));
oos.writeObject(user);
// 反序列化操作
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"));
User restored = (User) ois.readObject();

注意要点:transient标记的字段不参与序列化过程。serialVersionUID用于版本兼容性控制。

6. 实战:文件拷贝工具 + 配置读取

FileCopyTool.java:

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 文件拷贝工具:支持进度显示、速度统计
 */
public class FileCopyTool {
    
    /**
     * 拷贝文件
     * @param srcPath  源文件路径
     * @param destPath 目标文件路径
     */
    public static void copy(String srcPath, String destPath) {
        File srcFile = new File(srcPath);
        
        // 前置检查
        if (!srcFile.exists()) {
            System.out.println("源文件不存在:" + srcPath);
            return;
        }
        if (!srcFile.isFile()) {
            System.out.println("源路径不是文件:" + srcPath);
            return;
        }
        
        long fileSize = srcFile.length();
        long copied = 0;
        long startTime = System.currentTimeMillis();
        
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath))) {
            
            byte[] buffer = new byte[8192];  // 8KB 缓冲区
            int len;
            
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
                copied += len;
                
                // 每拷贝100KB打印一次进度
                if (copied % (100 * 1024) < 8192) {
                    showProgress(copied, fileSize, startTime);
                }
            }
            
            // 最终进度
            showProgress(copied, fileSize, startTime);
            System.out.println("n拷贝完成!");
            
        } catch (IOException e) {
            System.out.println("拷贝失败:" + e.getMessage());
        }
    }
    
    /**
     * 显示进度
     */
    private static void showProgress(long copied, long total, long startTime) {
        double percent = (double) copied / total * 100;
        long elapsed = System.currentTimeMillis() - startTime;
        double speed = copied / 1024.0 / (elapsed / 1000.0 + 0.001);  // KB/s
        
        System.out.printf("r进度: %.1f%% | 已拷贝: %.2f MB / %.2f MB | 速度: %.2f KB/s",
            percent,
            copied / 1024.0 / 1024.0,
            total / 1024.0 / 1024.0,
            speed);
    }
    
    public static void main(String[] args) {
        copy("source.zip", "copy.zip");
    }
}

ConfigReader.java(读取properties配置文件):

import java.io.*;
import java.util.Properties;
/**
 * 配置文件读取工具
 * properties文件格式:key=value
 */
public class ConfigReader {
    
    private Properties props = new Properties();
    
    /**
     * 加载配置文件
     */
    public void load(String filePath) {
        try (InputStream is = new FileInputStream(filePath)) {
            props.load(is);
            System.out.println("配置加载成功");
        } catch (IOException e) {
            System.out.println("配置加载失败:" + e.getMessage());
        }
    }
    
    /**
     * 获取配置值
     */
    public String getString(String key) {
        return props.getProperty(key);
    }
    
    public String getString(String key, String defaultValue) {
        return props.getProperty(key, defaultValue);
    }
    
    public int getInt(String key) {
        return Integer.parseInt(props.getProperty(key));
    }
    
    public boolean getBoolean(String key) {
        return Boolean.parseBoolean(props.getProperty(key));
    }
    
    /**
     * 保存配置
     */
    public void set(String key, String value) {
        props.setProperty(key, value);
    }
    
    public void save(String filePath) {
        try (OutputStream os = new FileOutputStream(filePath)) {
            props.store(os, "Updated on " + new java.util.Date());
            System.out.println("配置保存成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        ConfigReader config = new ConfigReader();
        
        // 创建测试配置文件
        try (PrintWriter pw = new PrintWriter("app.properties")) {
            pw.println("# 应用配置");
            pw.println("app.name=MyApp");
            pw.println("app.version=1.0");
            pw.println("db.host=localhost");
            pw.println("db.port=3306");
            pw.println("debug=true");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 读取配置
        config.load("app.properties");
        System.out.println("应用名:" + config.getString("app.name"));
        System.out.println("数据库端口:" + config.getInt("db.port"));
        System.out.println("调试模式:" + config.getBoolean("debug"));
        
        // 修改并保存
        config.set("app.version", "1.1");
        config.save("app.properties");
    }
}

最大感悟:IO操作务必关闭流,否则文件句柄泄漏可能导致系统无法分配新资源。try-with-resources是实现资源自动关闭的最佳实践。

热门栏目