BufferedOutputStream:把「多次写」变成「一次写」
写文件和读文件是镜像的。
读的时候,我们希望一次从磁盘读一大块数据到内存。 写的时候,我们希望先把数据攒在内存里,攒够了一大批再一次性写到磁盘。
BufferedOutputStream 就是干这个的。
核心原理
无缓冲时:
java
for (int i = 0; i < 8192; i++) {
fos.write(data[i]); // 每次 write 可能触发一次磁盘写入
}
// 写 8192 字节 = 8192 次系统调用有缓冲时:
java
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.dat"));
for (int i = 0; i < 8192; i++) {
bos.write(data[i]); // 先写到内存缓冲区
}
// 缓冲区满了才触发一次 write
// 写 8192 字节 = 1 次系统调用数据先到缓冲区,满了或 close 时才真正写到磁盘。
内部原理
java
public class BufferedOutputStream extends FilterOutputStream {
protected byte[] buf; // 缓冲区
protected int count; // 当前写入位置
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
// 缓冲区满了,先把内容写到磁盘
flushBuffer();
}
buf[count++] = (byte) b;
}
public synchronized void flush() throws IOException {
flushBuffer(); // 把缓冲区内容写到磁盘
out.flush(); // 再 flush 底层流
}
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count); // 批量写到底层流
count = 0;
}
}
}关键点:write(int b) 先检查缓冲区满不满,不满就往里写,满了就先 flushBuffer() 清空缓冲区,再写新数据。
基本用法
java
// 默认 8KB 缓冲区
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("data.bin"));
// 指定缓冲区大小
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("data.bin"), 16384);
// 典型用法
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("output.dat"))) {
byte[] data = "Hello World".getBytes();
bos.write(data);
bos.flush(); // 主动刷新到磁盘
}flush() 时机:什么时候需要
| 场景 | 是否需要 flush() |
|---|---|
| 写文件,正常 close | 不需要,close() 会自动 flush |
| 写网络流,需要即时发送 | 需要,每写一批就 flush |
| 写日志,需要实时落盘 | 需要,每条日志都 flush |
| 写文件但程序可能崩溃 | 需要,崩溃前先 flush |
java
// 日志场景:必须及时 flush
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("app.log"))) {
for (String log : logs) {
bos.write((log + "\n").getBytes());
bos.flush(); // 不 flush,程序崩溃时这行日志就丢了
}
}
// 普通文件场景:不需要手动 flush
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("data.bin"))) {
byte[] data = getData();
bos.write(data);
// close() 时自动 flush
}配合 BufferedInputStream 使用
文件拷贝的标准组合:
java
public static void copyFile(String src, String dst) throws IOException {
try (
BufferedInputStream in = new BufferedInputStream(
new FileInputStream(src));
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(dst))
) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
// close() 时 out 自动 flush,文件完整写入
}避坑指南
缓冲区太小
java
// ❌ 缓冲区太小:系统调用次数依然很多
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.dat"), 512); // 512 字节,每 512 字节写一次
// ✅ 合适大小:真正减少系统调用
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.dat"), 8192); // 8KB,每 8KB 写一次忘记 close()
java
// ❌ 数据还在缓冲区,程序退出了,文件不完整
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.dat"));
bos.write("data".getBytes());
// 没有 close(),缓冲区数据丢失
// ✅ 用 try-with-resources
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("out.dat"))) {
bos.write("data".getBytes());
}
// 自动 close() → 自动 flush() → 文件完整写入口诀:写网络/日志记得 flush,写文件交给 close,缓冲区 8KB 刚刚好。
