在Java的IO体系中,字节流是最基础的IO操作类型,但是直接使用FileInputStream和FileOutputStream进行读写时,每一次read或write调用都会触发一次底层系统的IO操作,当处理大文件或者频繁读写小数据时,这种频繁的系统调用会带来很高的性能损耗。BufferedInputStream和BufferedOutputStream作为缓冲字节流,通过在内存中维护一个缓冲区,将多次小批次的读写操作合并为少量的大批次底层IO,从而大幅降低系统调用的次数,提升IO操作的效率。

缓冲流的核心工作原理
BufferedInputStream内部维护了一个字节数组作为缓冲区,默认大小为8192字节(8KB)。当调用read方法读取数据时,会优先从缓冲区中获取数据,如果缓冲区为空,才会一次性从底层输入流中读取最多等于缓冲区大小的数据填充到缓冲区,再返回数据给调用者。这样就避免了每次读取一个字节就触发一次底层IO的问题。
BufferedOutputStream的原理类似,内部同样有一个字节数组缓冲区,调用write方法写入数据时,会先将数据写入缓冲区,当缓冲区被填满,或者手动调用flush方法,或者流关闭时,才会将缓冲区中的数据一次性写入到底层输出流中,减少底层write调用的次数。
BufferedInputStream常用方法说明
BufferedInputStream继承自FilterInputStream,构造方法和常用API如下:
- 构造方法:
BufferedInputStream(InputStream in)使用默认8KB缓冲区包装传入的输入流;BufferedInputStream(InputStream in, int size)使用指定大小的缓冲区包装输入流 - read():从缓冲区读取一个字节,返回字节的int值,如果到达流末尾返回-1
- read(byte[] b, int off, int len):从缓冲区读取最多len个字节到字节数组b的off位置开始的区域,返回实际读取的字节数
- close():关闭流,同时会关闭被包装的底层输入流,不需要单独关闭底层流
BufferedOutputStream常用方法说明
BufferedOutputStream继承自FilterOutputStream,构造方法和常用API如下:
- 构造方法:
BufferedOutputStream(OutputStream out)使用默认8KB缓冲区包装传入的输出流;BufferedOutputStream(OutputStream out, int size)使用指定大小的缓冲区包装输出流 - write(int b):将一个字节写入缓冲区
- write(byte[] b, int off, int len):将字节数组b中从off位置开始的len个字节写入缓冲区
- flush():将缓冲区中未写入的数据强制写入到底层输出流
- close():先调用flush方法,再关闭流和底层输出流
使用缓冲流实现文件复制示例
文件复制是IO操作中最常见的场景,下面使用BufferedInputStream和BufferedOutputStream实现一个高效的文件复制功能,对比不使用缓冲流的差异:
import java.io.*;
public class BufferedStreamCopyDemo {
public static void main(String[] args) {
// 源文件路径
String sourcePath = "D:/test_source.txt";
// 目标文件路径
String targetPath = "D:/test_target.txt";
// 调用复制方法
copyFileWithBuffer(sourcePath, targetPath);
}
/**
* 使用缓冲流复制文件
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
*/
private static void copyFileWithBuffer(String sourcePath, String targetPath) {
// 声明缓冲流对象,使用try-with-resources自动关闭流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourcePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetPath))) {
// 定义字节数组作为临时读取容器,大小为4KB
byte[] buffer = new byte[4096];
int len;
// 循环读取数据,直到到达文件末尾
while ((len = bis.read(buffer)) != -1) {
// 将读取到的数据写入输出缓冲流
bos.write(buffer, 0, len);
}
// 手动刷新缓冲区,确保数据全部写入
bos.flush();
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码中,我们使用try-with-resources语法自动管理流的生命周期,不需要手动编写close方法,避免流未关闭导致的资源泄漏问题。读取时每次从缓冲区获取最多4KB的数据,写入时先存入输出缓冲区,大幅减少了底层IO的调用次数。
缓冲流使用注意事项
缓冲区大小设置
默认的8KB缓冲区已经能满足大部分场景的需求,如果是处理大文件或者高并发的IO场景,可以适当调大缓冲区大小,比如设置为16KB、32KB,但是也不是越大越好,缓冲区过大会占用过多内存,需要根据实际场景调整。
流关闭顺序
如果手动关闭流,只需要关闭最外层的缓冲流即可,缓冲流的close方法会自动关闭被包装的底层流,不需要单独关闭FileInputStream或FileOutputStream,避免重复关闭的问题。
及时刷新输出流
如果在写入数据后没有关闭流,也没有调用flush方法,缓冲区中的数据可能不会写入到底层流中,导致数据丢失。如果是长时间运行的输出操作,建议定期调用flush方法将缓冲区数据刷入底层流。
缓冲流适用场景
缓冲流适合所有字节流的读写场景,尤其是处理大文件、频繁读写小数据的场景,对于已经自带缓冲功能的流(比如某些网络流),再包装缓冲流可能不会有明显的性能提升,反而会增加内存开销。
性能对比说明
我们可以通过简单的测试对比使用缓冲流和不使用缓冲流复制同一个100MB文件的耗时:
| 操作方式 | 复制100MB文件平均耗时 |
|---|---|
| 不使用缓冲流,单字节读写 | 约12000毫秒 |
| 不使用缓冲流,4KB字节数组读写 | 约300毫秒 |
| 使用缓冲流,单字节读写 | 约400毫秒 |
| 使用缓冲流,4KB字节数组读写 | 约280毫秒 |
从测试结果可以看出,即使使用字节数组批量读写,包装缓冲流后仍然能带来一定的性能提升,而如果是单字节读写场景,缓冲流的性能提升会非常明显。
BufferedInputStreamBufferedOutputStreamJava_IO缓冲流IO_优化修改时间:2026-06-11 15:21:33