Skip to content
第 31 / 250 章后端⏱ 10 分钟阅读

第 31 章:IO 流基础

学习目标

  • 掌握 File / Path 文件操作
  • 理解字节流与字符流的区别
  • 掌握缓冲流与 NIO 便捷 API

一、File 与 Path

java
// 老 API:java.io.File
File file = new File("data/user.txt");
file.exists();          // 是否存在
file.isDirectory();     // 是否目录
file.length();          // 字节数
file.getAbsolutePath(); // 绝对路径
file.mkdirs();          // 创建多级目录
file.delete();          // 删除

// ✅ 新 API:java.nio.file(JDK 7+,推荐)
Path path = Path.of("data/user.txt");
Files.exists(path);
Files.size(path);
Files.createDirectories(path.getParent());
Files.deleteIfExists(path);

为什么用 NIO? File 的 API 出错时只返回 false,不告诉你原因;Files 抛异常带明确信息。

二、IO 流全景

维度字节流字符流
单位byte(8 位)char(16 位)
适用图片、视频、任意二进制文本
基类InputStream / OutputStreamReader / Writer
编码不处理自动按字符集解码

判断标准:能用记事本打开看懂的 → 字符流;否则 → 字节流。

三、读写文本文件

java
// ✅ 最简单(JDK 11+):小文件一行搞定
String content = Files.readString(Path.of("a.txt"));
Files.writeString(Path.of("b.txt"), content);

// ✅ 按行读(大文件用 Stream,不会一次性加载到内存)
try (Stream<String> lines = Files.lines(Path.of("big.log"))) {
    lines.filter(l -> l.contains("ERROR"))
         .forEach(System.out::println);
}

// ✅ 传统缓冲流写法(要控制编码时)
try (BufferedReader br = Files.newBufferedReader(
        Path.of("a.txt"), StandardCharsets.UTF_8)) {   // ① 显式指定编码
    String line;
    while ((line = br.readLine()) != null) {           // ② readLine 返回 null 表示读完
        System.out.println(line);
    }
}

⚠️ 编码坑:不指定编码时用系统默认(Windows 中文是 GBK),跨平台会乱码。永远显式写 UTF-8

四、复制文件(字节流)

java
// ✅ 一行搞定
Files.copy(Path.of("src.png"), Path.of("dst.png"),
           StandardCopyOption.REPLACE_EXISTING);

// 手动实现(理解原理)
try (InputStream in = Files.newInputStream(Path.of("src.png"));
     OutputStream out = Files.newOutputStream(Path.of("dst.png"))) {

    byte[] buffer = new byte[8192];      // ① 8KB 缓冲区,减少系统调用次数
    int len;
    while ((len = in.read(buffer)) != -1) {  // ② read 返回实际读取字节数,-1 表示结束
        out.write(buffer, 0, len);           // ③ 只写实际读到的长度,不能写整个 buffer
    }
}

为什么用 8192? 大多数文件系统块大小是 4KB,8KB 是吞吐与内存的经验平衡点,JDK 内部默认也是这个值。

五、缓冲流为什么快

性能差距:读 1MB 文件,无缓冲逐字节读比缓冲读慢 上百倍

六、序列化

java
public class User implements Serializable {          // ① 必须实现 Serializable
    private static final long serialVersionUID = 1L; // ② 版本号,改类结构时兼容判断依据

    private String name;
    private transient String password;               // ③ transient 字段不参与序列化
}

// 写对象
try (ObjectOutputStream oos = new ObjectOutputStream(
        Files.newOutputStream(Path.of("user.dat")))) {
    oos.writeObject(user);
}

// 读对象
try (ObjectInputStream ois = new ObjectInputStream(
        Files.newInputStream(Path.of("user.dat")))) {
    User u = (User) ois.readObject();
}

⚠️ 生产建议:Java 原生序列化有安全漏洞(反序列化 RCE)且跨语言不通,实际项目用 JSON(Jackson)Protobuf

七、常见 IO 场景速查

java
// 读 classpath 资源(读配置文件常用)
try (InputStream in = getClass().getResourceAsStream("/config.yml")) {
    String s = new String(in.readAllBytes(), StandardCharsets.UTF_8);
}

// 遍历目录
try (Stream<Path> paths = Files.walk(Path.of("src"))) {
    paths.filter(p -> p.toString().endsWith(".java"))
         .forEach(System.out::println);
}

// 追加写
Files.writeString(Path.of("log.txt"), "new line\n",
                  StandardOpenOption.CREATE, StandardOpenOption.APPEND);

八、本章小结

要点关键
Path / Files优先于老的 File
字节流二进制,InputStream
字符流文本,Reader,必须指定 UTF-8
缓冲流减少系统调用,快上百倍
try-with-resources流一律这样写
序列化生产用 JSON,别用原生

动手练习

练习 1:基础题

统计一个 .log 文件中包含 ERROR 的行数。

练习 2:进阶题

写一个方法递归统计目录下所有 .java 文件的总行数,输出 Top 10 最长文件。


下一章第 32 章:IO 流进阶

本站基于 VitePress 构建 · 由 Codebook 团队维护