文件流
文件流是最常用的 IO 流之一,用于从文件读取字节或向文件写入字节。文件流本身只关心字节,不理解文本编码,也不提供额外缓冲能力。
# 1. FileInputStream
FileInputStream 用于从文件读取原始字节。
try (FileInputStream input = new FileInputStream("data.bin")) {
byte[] buffer = new byte[8192];
int len;
while ((len = input.read(buffer)) != -1) {
// process buffer[0, len)
}
}
适用场景:
- 读取图片、音频、视频。
- 读取压缩包。
- 读取任意二进制文件。
- 作为其他处理流的底层节点流。
# 2. FileOutputStream
FileOutputStream 用于向文件写入原始字节。
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
try (FileOutputStream output = new FileOutputStream("data.txt")) {
output.write(data);
}
默认情况下,如果目标文件已存在,会覆盖原文件。
如果希望追加写入,可以使用追加模式:
try (FileOutputStream output = new FileOutputStream("app.log", true)) {
output.write("new log\n".getBytes(StandardCharsets.UTF_8));
}
# 3. 文件复制
传统文件流复制:
try (FileInputStream input = new FileInputStream("source.dat");
FileOutputStream output = new FileOutputStream("target.dat")) {
byte[] buffer = new byte[8192];
int len;
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
}
现代代码更推荐:
Files.copy(Path.of("source.dat"), Path.of("target.dat"), StandardCopyOption.REPLACE_EXISTING);
Files.copy 可读性更好,也减少手写循环出错的概率。
# 4. 文件流没有缓冲
FileInputStream 和 FileOutputStream 直接面向文件描述符,频繁小块读写性能不理想。生产代码中经常用缓冲流包装:
try (InputStream input = new BufferedInputStream(new FileInputStream("source.dat"));
OutputStream output = new BufferedOutputStream(new FileOutputStream("target.dat"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
}
# 5. 常见异常
使用文件流时常见异常包括:
FileNotFoundException:文件不存在、路径错误、没有权限、目标是目录。AccessDeniedException:使用 NIO API 时常见,表示权限不足。IOException:读取、写入或关闭过程中发生底层 IO 错误。
不要简单吞掉异常,至少要记录文件路径、操作类型、异常栈,方便排查。
# 6. 生产实践建议
- 文本读写不要直接使用文件流,除非你明确处理编码。
- 大文件复制优先考虑
Files.copy、FileChannel.transferTo。 - 频繁小写入时一定要加缓冲。
- 不要忘记关闭文件流,否则可能导致文件描述符泄漏。
- 写关键文件时要考虑临时文件、刷盘、原子移动,避免写一半失败导致文件损坏。
# 专家实践清单
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 小文件复制 | Files.copy | 语义清晰,错误处理完整 |
| 中大文件顺序读写 | 文件流 + 缓冲区 | 控制内存占用 |
| 文本文件 | Files.newBufferedReader | 文件流本身不处理编码 |
| 高效搬运 | FileChannel.transferTo | 可利用零拷贝能力 |
| 关键配置写入 | 临时文件 + 原子移动 | 避免半写入 |
文件流的角色:
磁盘文件
│
▼
FileInputStream / FileOutputStream
│ 原始字节
▼
缓冲、转换、解析、业务处理
# Tips 快问快答
Q:文件流属于节点流还是处理流? A:节点流,直接连接磁盘文件。
Q:文件流是否自带缓冲?
A:不自带,高频读写通常要包装缓冲流或使用 Files 便捷 API。
Q:文件流能处理字符编码吗? A:不能。它只处理字节,文本编码要交给转换流或字符流。
Q:FileOutputStream 默认是覆盖还是追加?
A:默认覆盖;构造器传 append=true 时才追加。
Q:为什么写文件可能出现半文件? A:进程崩溃、磁盘满、权限异常等可能发生在写入中途。
Q:文件流忘记关闭有什么后果? A:文件句柄泄漏,可能导致文件无法删除、句柄耗尽或数据未刷新。
Q:大文件复制一定要手写循环吗?
A:不一定,Files.copy、FileChannel.transferTo 经常更合适。
Q:文件流适合直接暴露给上层业务吗? A:通常不适合,应封装资源关闭、异常上下文和边界校验。
# 7. 总结
文件流是连接磁盘文件的基础节点流。它简单、直接、通用,但只处理字节,不处理编码,也不自带缓冲。日常开发中通常把文件流作为底层能力,再配合缓冲流、转换流或 NIO 工具类使用。