Java中BufferedInputStream与无缓冲流的性能对比及疑问
文件IO缓冲性能测试与疑问解答
实验背景
我创建了一个包含100万个0的980K文件,使用默认8192字节缓冲区对比缓冲与无缓冲输入输出的文件复制速度,发现同时启用输入输出缓冲可显著提升性能。
我理解超过缓冲区大小的大文件读取可能抵消部分缓冲优势,但仍不解为何缓冲性能始终优于无缓冲(至少系统调用更少),目前仅发现缓冲会占用额外内存这一缺点。
核心问题
- 除内存受限环境外,为何不始终同时使用输入输出缓冲?
- 为何输出缓冲能加快文件复制速度?
测试代码示例
我的测试包含t1()、t2()、t3()、t4()等函数,以下是其中一个示例:
static void t2() { System.out.println("Copy file using BufferedInputStream (8192), with BufferOut"); long startTime = System.nanoTime(); try (BufferedInputStream br = new BufferedInputStream(new FileInputStream("text.txt")); var bOut = new BufferedOutputStream(new FileOutputStream("text_3.txt"))) { int i; while ((i = br.read()) != -1) { bOut.write(i); } } catch (IOException e) { System.out.println(e); } long endTime = System.nanoTime(); long duration = (endTime - startTime) / 1000000; System.out.println("Duration: " + duration + " millisecond"); }
额外发现与疑问
我尝试结合缓冲输入输出使用in.transferTo,性能更优(仅需个位数毫秒)。另外我计划测试不同缓冲区大小,了解到最优大小取决于文件系统块大小(我的为4096字节),请问应设为4096还是越大越好?
问题解答
1. 除内存受限环境外,为何不始终同时使用输入输出缓冲?
虽然缓冲在绝大多数场景下能提升性能,但确实存在不需要甚至不适合用缓冲的场景:
- 小文件一次性读写:如果文件大小远小于缓冲区,比如几KB的配置文件,直接一次性读写的开销和缓冲几乎无差别,甚至缓冲的初始化、内存分配反而会增加微小额外成本。
- 实时性要求极高的场景:比如日志系统需要立刻将内容写入磁盘(避免缓冲满才刷新导致日志丢失),缓冲流默认的满缓冲或定时刷新机制会导致数据延迟写入,不符合实时性要求。
- 已做上层缓冲的场景:如果业务代码已经自己实现了批量读写逻辑(比如每次读取10KB数据再处理),再套一层JDK缓冲流属于重复缓冲,会增加内存占用和数据拷贝开销。
- 特殊IO设备:比如网络套接字或某些字符设备,本身底层已做缓冲,或者需要逐字节处理(比如串口通信),缓冲流的批量处理反而会打乱数据时序。
2. 为何输出缓冲能加快文件复制速度?
核心原因是减少系统调用次数和磁盘IO次数:
- 无缓冲的
FileOutputStream.write(int)每次调用都会触发一次系统级write调用,而系统调用需要在用户态和内核态之间切换,这个切换开销远大于内存中拷贝数据的开销。 - 缓冲输出流会先把数据攒到内存缓冲区,直到缓冲区满(或主动调用
flush()),才一次性发起系统调用将整个缓冲区数据写入磁盘。以你的980K文件为例,系统调用次数从百万级降到约120次,大幅减少态切换开销。 - 磁盘是块设备,按块读写效率最高。缓冲输出能保证每次写入大小对齐磁盘块,避免磁盘“碎片化”写入,进一步提升IO效率。
关于缓冲区大小的选择:不是越大越好
最优缓冲区大小确实和文件系统块强相关,但并非简单设为4096或者越大越好:
- 优先对齐块大小的整数倍:你的文件系统块是4096字节,缓冲区设为4096、8192(默认值)、16384这类4096的整数倍都可以,能保证每次读写对应整数个磁盘块,避免跨块的部分读写。
- 不要过大:当缓冲区超过几十MB,性能提升会非常有限甚至下降。更大的缓冲区会占用更多内存,可能导致内存页交换(系统内存紧张时),反而增加开销;另外JVM对大内存块的分配管理也有额外成本。
- 结合实际场景测试:如果是批量复制大文件,可测试8K、16K、32K等大小,大多数系统下8K到64K区间能获得不错性能。
- 关于
transferTo:这个方法性能更好是因为它利用了操作系统的零拷贝机制,数据不需要在用户态和内核态之间来回拷贝,直接从磁盘读取到内核缓冲区再写入目标文件,跳过用户态的缓冲拷贝,所以速度更快。
内容的提问来源于stack exchange,提问作者7589
相关产品推荐
相关产品推荐

