1. 引言
在 Java 的 I/O 体系中,BufferedOutputStream 是一个非常重要的缓冲输出流。它通过在内存中维护一个缓冲区,减少底层系统调用的次数,从而显著提升文件写入和网络传输的性能。本文将从原理、构造方法、常用方法、性能对比和最佳实践几个方面,全面解析 BufferedOutputStream 的使用方法。
2. BufferedOutputStream 概述
BufferedOutputStream 位于 java.io 包中,继承自 FilterOutputStream。它本身并不直接操作文件或网络,而是包装一个底层的输出流(如 FileOutputStream),在写入数据时先将数据暂存到内部缓冲区,待缓冲区写满或显式调用 flush() 时,才一次性将缓冲区中的数据写入底层流。
这种设计带来的核心优势是:减少对底层系统资源的调用次数,降低 I/O 开销,提升写入效率。
3. 类继承关系
理解 BufferedOutputStream 在类继承体系中的位置,有助于更好地掌握它的行为。
java
java.lang.Object
└── java.io.OutputStream
└── java.io.FilterOutputStream
└── java.io.BufferedOutputStream
4. 构造方法
BufferedOutputStream 提供了两个构造方法,分别用于指定默认缓冲区大小和自定义缓冲区大小。
| 构造方法 | 说明 |
|---|---|
BufferedOutputStream(OutputStream out) |
创建一个默认缓冲区大小为 8192 字节(8KB)的缓冲输出流。 |
BufferedOutputStream(OutputStream out, int size) |
创建一个指定缓冲区大小的缓冲输出流,size 必须大于 0。 |
示例代码如下:
java
// 使用默认缓冲区大小(8KB)
BufferedOutputStream bos1 = new BufferedOutputStream(new FileOutputStream("data.txt"));
// 自定义缓冲区大小为 16KB
BufferedOutputStream bos2 = new BufferedOutputStream(new FileOutputStream("data.txt"), 16384);
5. 常用方法
BufferedOutputStream 继承并实现了 OutputStream 的核心方法,同时增加了缓冲相关的行为。
write(int b):将单个字节写入缓冲区。如果缓冲区已满,会先触发一次底层写入。write(byte[] b, int off, int len):将字节数组的一部分写入缓冲区。如果数据长度大于等于缓冲区容量,会直接写入底层流。flush():将缓冲区中剩余的数据强制写入底层流,并刷新底层流。close():先调用flush(),然后关闭底层输出流,释放相关资源。
6. 完整使用示例
下面通过一个完整的示例,演示如何使用 BufferedOutputStream 将字符串写入文件。
java
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class BufferedOutputStreamExample {
public static void main(String[] args) {
String content = "Hello, BufferedOutputStream!";
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("output.txt"))) {
bos.write(content.getBytes(StandardCharsets.UTF_8));
// 写入完成后刷新缓冲区
bos.flush();
System.out.println("文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,使用了 try-with-resources 语法,程序结束后会自动调用 close() 方法,无需手动关闭流。
7. 性能对比:缓冲 vs 非缓冲
为了直观展示 BufferedOutputStream 的性能优势,下面通过一个简单的基准测试,对比使用缓冲和不使用缓冲写入大量数据的耗时差异。
java
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class PerformanceCompare {
private static final int DATA_SIZE = 10 * 1024 * 1024; // 10MB
public static void main(String[] args) throws IOException {
byte[] data = new byte[DATA_SIZE];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 128);
}
// 非缓冲写入
long start1 = System.currentTimeMillis();
try (OutputStream os = new FileOutputStream("no_buffer.bin")) {
os.write(data);
}
long end1 = System.currentTimeMillis();
System.out.println("非缓冲写入耗时:" + (end1 - start1) + " ms");
// 缓冲写入
long start2 = System.currentTimeMillis();
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("with_buffer.bin"))) {
bos.write(data);
}
long end2 = System.currentTimeMillis();
System.out.println("缓冲写入耗时:" + (end2 - start2) + " ms");
}
}
通常情况下,缓冲写入的耗时远低于非缓冲写入,尤其是在写入大量小数据块时,性能差距更加明显。
8. 缓冲区大小对性能的影响
缓冲区大小直接影响写入性能。缓冲区过小会导致频繁的底层写入,缓冲区过大则会占用较多内存。默认的 8KB 缓冲区在大多数场景下已经足够,但在处理大文件时,可以适当增大缓冲区以进一步提升性能。
java
// 使用 64KB 缓冲区处理大文件
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("large.bin"), 64 * 1024)) {
// 写入数据
}
9. 注意事项与最佳实践
- 及时调用 flush() :在需要确保数据落盘或发送到网络时,应显式调用
flush(),避免数据滞留在缓冲区中。 - 使用 try-with-resources:确保流在使用完毕后自动关闭,避免资源泄漏。
- 合理设置缓冲区大小:根据实际场景调整缓冲区大小,平衡内存占用和写入性能。
- 避免重复包装 :不要对同一个
BufferedOutputStream进行多次包装,以免造成不必要的开销。 - 注意字符编码 :写入文本时,应明确指定字符集(如
StandardCharsets.UTF_8),避免乱码问题。
10. 总结
BufferedOutputStream 是 Java I/O 中提升写入性能的利器。通过合理使用缓冲区、及时刷新和正确关闭资源,可以显著提高文件写入和网络传输的效率。在实际开发中,建议优先使用缓冲输出流,并结合具体场景调整缓冲区大小,以获得最佳性能表现。