IO常见坑
Java IO 的 API 并不难,难的是在真实环境中避免细节问题。下面整理一些高频坑点。
# 1. 忘记关闭流
问题:
- 文件描述符泄漏。
- 文件无法删除或移动。
- Socket 连接不释放。
- 数据没有刷新出去。
建议:
try (InputStream input = Files.newInputStream(path)) {
// use input
}
# 2. 处理 buffer 时忽略 len
错误:
while ((len = input.read(buffer)) != -1) {
output.write(buffer);
}
正确:
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
最后一次读取通常不会填满整个 buffer。
# 3. 用字符流处理二进制文件
错误:
Reader reader = new FileReader("image.png");
字符流会进行编码解码,可能破坏二进制数据。图片、视频、压缩包必须使用字节流。
# 4. 依赖默认字符集
错误:
new String(bytes);
Files.readString(path);
建议:
new String(bytes, StandardCharsets.UTF_8);
Files.readString(path, StandardCharsets.UTF_8);
# 5. 大文件一次性读入内存
错误:
List<String> lines = Files.readAllLines(bigFile);
建议:
try (BufferedReader reader = Files.newBufferedReader(bigFile, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
process(line);
}
}
# 6. write 后以为一定落盘
write() 写到 Java 或操作系统缓冲区,并不代表数据已经物理落盘。
关键数据要考虑:
flush()FileChannel.force()- 临时文件 + 原子移动
- 事务日志
- 校验和
# 7. 忽略符号链接和路径穿越
处理用户上传或下载路径时,不能简单拼接:
Path target = base.resolve(userInput);
需要规范化并校验是否仍在允许目录下:
Path normalized = base.resolve(userInput).normalize();
if (!normalized.startsWith(base)) {
throw new SecurityException("invalid path");
}
# 8. NIO 写入假设一次完成
错误:
channel.write(buffer);
正确:
while (buffer.hasRemaining()) {
channel.write(buffer);
}
非阻塞网络场景还不能简单自旋,需要注册写事件并维护待发送缓冲。
# 专家实践清单
| 坑点 | 现象 | 防御方式 |
|---|---|---|
| 资源泄漏 | too many open files | try-with-resources |
| 默认编码 | 本地正常线上乱码 | 显式 Charset |
| 大文件入内存 | OOM | 流式处理 |
| 写半文件 | 文件损坏 | 临时文件 + 原子移动 |
| 路径穿越 | 任意文件读写 | normalize + 根目录校验 |
| 半包半写 | 协议错乱 | 明确消息边界和写队列 |
排查顺序:
先看异常和日志上下文
▼
确认资源是否关闭
▼
确认编码和文件大小
▼
确认路径和权限
▼
确认网络边界和超时
# Tips 快问快答
Q:IO 代码最常见的 bug 是什么? A:资源未关闭、编码错误、大文件一次性加载和路径未校验。
Q:为什么 read 不能假设读满缓冲区?
A:IO 每次返回的字节数取决于底层可用数据。
Q:为什么上传文件要校验路径?
A:防止 ../ 路径穿越写入非授权目录。
Q:写文件为什么要考虑原子性? A:进程崩溃或磁盘满会留下半写入文件。
Q:网络 IO 为什么必须有超时? A:对端可能长时间不响应,线程和连接会被占住。
Q:为什么不能吞掉 IOException? A:IO 失败通常意味着外部环境异常,吞掉会导致数据不一致。
Q:文件权限问题如何定位? A:看异常类型、运行用户、目录权限、挂载方式和 SELinux/容器限制。
Q:资深工程师写 IO 代码最关注什么? A:资源生命周期、数据边界、失败恢复、安全校验和可观测性。
# 9. 总结
IO 常见坑大多来自资源生命周期、边界长度、字符集、大文件、缓冲语义和安全校验。写 IO 代码时要有“外部资源不可信、外部环境不稳定、数据边界必须明确”的意识。
上次更新: 2026/06/25, 14:19:18