核心问题是频繁系统调用和上下文切换;8KB缓冲区是实用起点,须用read()返回值控制write(),并用try-with-resources确保流正确关闭。

大文件复制慢,核心问题不在磁盘本身,而在于频繁的系统调用和上下文切换。单字节读写一个 500MB 文件,会触发约 5.2 亿次 I/O 调用 —— CPU 大量时间花在切换上,而不是搬数据。用缓冲区,就是把“一趟搬一粒米”改成“一趟搬一袋米”,效率跃升。
选对缓冲区大小:8KB 是实用起点
缓冲区不是越大越好,关键看平衡:
- 8192 字节(8KB) 是多数场景下的推荐值:兼顾吞吐、内存占用和 JVM 默认堆配置,实测提升显著且稳定
- 小文件或低延迟场景(如日志切片),可试 4096
- 确定是顺序大文件、内存充裕且已配
-XX:+UseLargePages,再考虑 65536,但收益仅 5%~10%,边际递减明显 - 超过 64KB 后性能基本持平,还可能挤占其他对象空间,增加 GC 压力
必须用 read() 返回值控制 write()
这是最容易出错、也最常导致文件损坏的地方:
-
int len = in.read(buffer)的返回值才是**实际读到的字节数**,末尾往往小于buffer.length - 若直接
out.write(buffer)或out.write(buffer, 0, buffer.length),就会把缓冲区里残留的旧数据(比如 0x00)一并写出,造成内容污染 - 正确写法始终是:
out.write(buffer, 0, len)
务必用 try-with-resources 管理流
手动 close 容易遗漏,尤其在异常路径下:
立即学习“Java免费学习笔记(深入)”;
- 句柄泄漏会导致后续操作报
java.io.IOException: Too many open files - 标准写法确保流无论成功或异常都会关闭:
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst)) { ... } - 缓冲数组
byte[]是普通对象,不持有资源,无需 close
缓冲流 vs 手动 byte[] 缓冲:按需选择
二者底层都靠 8KB 缓冲,纯拷贝性能接近,但侧重点不同:
-
直接用
byte[] + FileInputStream/FileOutputStream:更轻量、GC 压力略低,适合高频小文件批量拷贝;还可边读边校验、遇到特定字节提前终止 -
用
BufferedInputStream/BufferedOutputStream:封装好、不易出错,适合链式处理(如解密→解压→写入),代码更简洁 - 注意:BufferedInputStream 默认缓冲区是 8KB,但某些 JDK 版本文档误标为 8MB,以源码和实测为准


















