Skip to content

IO 流关闭规范

你有没有踩过这个坑:

程序写了一个文件,close() 之后发现文件是空的。

或者这个:

程序跑了一会儿,抛了个「Too many open files」异常。

这些都是 IO 流没有正确关闭导致的问题。

这个泄漏坑我踩过

当年写一个数据导出功能,导出 100 万条数据到 CSV 文件。测试没问题,上线后发现:

  • 文件能正常生成
  • 但用 Excel 打开时报错:文件损坏
  • 排查半天发现:最后一批数据没写进去

原因是代码里用了 BufferedWriter,但没等它 flush 就直接 return 了,数据还在缓冲区里。

这是 IO 流关闭不规范最常见的症状:数据丢失

为什么必须关闭

  • 文件句柄是操作系统资源,数量有限(Linux 默认 1024)
  • 未关闭的流可能导致文件被锁
  • 缓冲区数据可能未写入磁盘

try-with-resources:标准写法

Java 7 引入的 try-with-resources 是正确的关闭方式:

java
// ✅ 标准写法
try (InputStream in = new FileInputStream("data.bin")) {
    byte[] buf = new byte[8192];
    while (in.read(buf) != -1) {
        // 处理
    }
}
// 自动关闭,异常也关闭

// ✅ 多层嵌套流
try (
    BufferedInputStream in = new BufferedInputStream(
        new FileInputStream("data.bin"));
    BufferedOutputStream out = new BufferedOutputStream(
        new FileOutputStream("out.bin"))
) {
    // 只需关外层,底层自动关闭
    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) != -1) {
        out.write(buf, 0, len);
    }
}

finally 方式:老式但正确

java
// 老式写法,功能正确但繁琐
InputStream in = null;
try {
    in = new FileInputStream("data.bin");
    byte[] buf = new byte[8192];
    while (in.read(buf) != -1) {
        // 处理
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (in != null) {
        try {
            in.close();
        } catch (IOException e) {
            // ignore
        }
    }
}

不推荐的写法

java
// ❌ 错误:异常时不关闭
InputStream in = new FileInputStream("data.bin");
in.read();
// 如果 read() 抛异常,close() 永远不执行
in.close();

// ❌ 错误:只关最外层不够(对于某些流)
// 实际上 BufferedInputStream 会自动关闭底层,但其他处理流不一定

// ❌ 错误:close() 失败时抛异常,掩盖原始异常
try {
    // IO 操作
    out.close(); // out.close() 失败,原始异常被掩盖
} catch (IOException e) {
    // out.close() 失败,原始异常被掩盖
}

flush() vs close()

方法作用调用时机
flush()把缓冲区数据写出需要即时落盘时(网络、日志)
close()先 flush,再释放资源用完流时
java
// ✅ close() 时自动 flush
try (BufferedOutputStream out = new BufferedOutputStream(
        new FileOutputStream("data.bin"))) {
    out.write("data".getBytes());
} // close() 自动 flush

// ✅ 需要及时落盘时主动 flush
try (BufferedOutputStream out = new BufferedOutputStream(
        new FileOutputStream("log.txt"))) {
    for (String log : logs) {
        out.write((log + "\n").getBytes());
        out.flush(); // 日志需要立即写入
    }
}

自动关闭的陷阱

装饰器链的关闭行为

java
// ✅ BufferedInputStream / BufferedOutputStream
// close() 时自动关闭底层流
try (BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("data.bin"))) {
    // 只需关 bis
} // FileInputStream 自动关闭

// ❌ 但不是所有装饰器都这样
// 建议查阅文档确认

关闭顺序

java
// ✅ 多流关闭顺序无所谓(try-with-resources 自动处理)
try (
    FileInputStream fis = new FileInputStream("src");
    FileOutputStream fos = new FileOutputStream("dst")
) {
    // 操作
} // 自动关闭,先开的后关

// ❌ 手动关闭时注意顺序
InputStream in = null;
OutputStream out = null;
try {
    in = new FileInputStream("src");
    out = new FileOutputStream("dst");
    // 操作
} finally {
    if (out != null) out.close(); // 先关 out
    if (in != null) in.close();   // 后关 in
}

总结

┌─────────────────────────────────────────────────────────────────┐
│  IO 流关闭规范:                                                  │
│                                                                 │
│  1. 永远用 try-with-resources                                     │
│  2. close() 时自动 flush,无需手动 flush                         │
│  3. 需要即时落盘时(网络、日志)主动 flush()                       │
│  4. 缓冲区数据在 close() 时自动写出                               │
│                                                                 │
│  口诀:用完就关,关了就 flush                                    │
└─────────────────────────────────────────────────────────────────┘

基于 VitePress 构建