缓冲流
缓冲流用于减少程序和底层 IO 设备之间的直接交互次数,是 Java IO 中最常见的性能优化手段之一。
# 1. 为什么需要缓冲
磁盘和网络 IO 的成本远高于内存操作。如果程序每次只读写一个字节或一个字符,就可能触发大量系统调用。
缓冲流会在内存中维护一个缓冲区:
程序 <-> 内存缓冲区 <-> 文件或网络
读取时,一次从底层读取一批数据到缓冲区;程序再从缓冲区中取数据。写入时,先写入缓冲区,缓冲区满了或刷新时再写到底层。
# 2. 字节缓冲流
字节缓冲流包括:
BufferedInputStreamBufferedOutputStream
try (InputStream input = new BufferedInputStream(new FileInputStream("source.bin"));
OutputStream output = new BufferedOutputStream(new FileOutputStream("target.bin"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
}
即使外层已经有缓冲流,代码中仍然使用数组缓冲区是常见写法,可以减少方法调用次数,并让数据处理逻辑更明确。
# 3. 字符缓冲流
字符缓冲流包括:
BufferedReaderBufferedWriter
BufferedReader 最常用的能力是按行读取:
try (BufferedReader reader = Files.newBufferedReader(Path.of("app.log"), StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
BufferedWriter 支持写入换行:
try (BufferedWriter writer = Files.newBufferedWriter(Path.of("result.txt"), StandardCharsets.UTF_8)) {
writer.write("hello");
writer.newLine();
writer.write("world");
}
# 4. flush 的作用
写入缓冲流时,数据可能暂时停留在内存缓冲区中。flush() 用于强制把缓冲区内容写到底层目标。
writer.write("important message");
writer.flush();
需要显式 flush() 的场景:
- 网络通信中希望对端立即收到数据。
- 命令行交互中希望立即显示提示。
- 长时间不关闭流但需要周期性写出。
文件写入中,如果马上关闭流,close() 会触发刷新。但重要数据仍然要考虑操作系统页缓存和磁盘刷盘,不是调用 flush() 就一定物理落盘。
# 5. 缓冲区大小如何选择
JDK 默认缓冲区通常已经适合大部分场景。自定义缓冲区时可以从 8KB、16KB、64KB 开始,不要盲目越大越好。
缓冲区过小:
- 系统调用次数多。
- 吞吐量低。
缓冲区过大:
- 占用更多内存。
- 对小文件没有明显收益。
- 高并发场景下可能增加 GC 压力。
性能优化要结合文件大小、并发数、磁盘类型、网络情况和实际压测结果。
# 6. 常见误区
BufferedReader.readLine()会去掉行尾换行符。BufferedWriter.newLine()写入的是系统相关换行符。- 使用缓冲输出流后,不关闭也不刷新可能导致数据没有真正写出。
- 已经使用
Files.readAllLines时,再加缓冲没有意义,因为数据已经整体读入内存。
# 专家实践清单
| 使用点 | 建议 | 原因 |
|---|---|---|
| 缓冲大小 | 从 8KB、16KB、64KB 试起 | 结合文件大小和吞吐压测 |
| 输出刷新 | 关闭前刷新或直接关闭 | 防止数据停留在内存缓冲区 |
| 按行读取 | 使用 BufferedReader | 避免一次性加载文本 |
| 高频小写入 | 使用 BufferedWriter | 减少系统调用 |
缓冲流的数据路径:
业务代码
│ 小块读写
▼
内存缓冲区
│ 批量读写
▼
底层文件/网络流
# Tips 快问快答
Q:缓冲流解决什么性能问题? A:减少频繁底层 IO 调用,把多次小读写合并成较少的大读写。
Q:缓冲区越大越好吗? A:不是。过大浪费内存,过小系统调用频繁,需要压测选择。
Q:flush 的作用是什么?
A:把缓冲区中尚未写出的数据推到底层输出流。
Q:关闭缓冲输出流会自动 flush 吗? A:通常会,关闭前会刷新缓冲并关闭底层流。
Q:BufferedReader.readLine 适合什么场景?
A:适合按行处理日志、配置、CSV 等文本。
Q:已经使用 Files.readAllBytes 还需要缓冲吗?
A:不需要,数据已经一次性读入内存。
Q:缓冲流会改变数据内容吗? A:不会,它只改变读写批量和性能特征。
Q:最常见的缓冲流错误是什么? A:写完不关闭不刷新,导致数据没有真正写出。
# 7. 总结
缓冲流是 Java IO 中最常用的增强流。它通过减少底层 IO 次数提升性能,特别适合频繁小块读写。生产代码中,文件流、网络流通常都会配合缓冲流使用。