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 │
└─────────────────────────────────────────────────────────────────┘