Skip to content

缓冲区大小调优

缓冲区太小系统调用多,太大内存浪费。

这一节告诉你不同场景的最佳缓冲区大小,以及背后的原理。

系统调用开销

每次 read() / write() 都涉及用户态到内核态的切换,开销巨大。

系统调用开销 ≈ 数千个 CPU 周期
一次内存操作 ≈ 几十个 CPU 周期

差距是 100 倍

缓冲区越大,系统调用次数越少。但缓冲区太大会浪费内存、增加 GC 压力。

权衡原则

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  缓冲区太小                                                      │
│    ├── 优点:内存占用低                                          │
│    └── 缺点:系统调用次数多,性能差                              │
│                                                                 │
│  缓冲区太大                                                      │
│    ├── 优点:系统调用次数少,性能好                              │
│    └── 缺点:内存占用高,GC 压力大,缓存污染                      │
│                                                                 │
│  最佳平衡点:8192 字节(8KB)                                     │
│    ├── 匹配大多数文件系统的块大小(4KB~64KB)                    │
│    ├── 内存占用可接受                                            │
│    └── 系统调用次数大幅减少                                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

场景化建议

场景推荐缓冲区大小理由
普通文件读写8192 字节(8KB)通用最佳值
大文件拷贝1MB ~ 8MB减少大文件的系统调用次数
网络 IO1460 或 8192 字节匹配 MTU(1500 字节)
数据库文件4KB ~ 64KB匹配数据库块大小
小文件频繁读写512 ~ 4096 字节避免内存浪费

测试验证

不同文件大小的最优缓冲区大小不同。可以用这个代码做基准测试:

java
public static void benchmarkBufferSize(String path) throws IOException {
    for (int bufferSize : new int[]{512, 1024, 4096, 8192, 16384, 65536}) {
        long start = System.nanoTime();
        try (
            BufferedInputStream in = new BufferedInputStream(
                new FileInputStream(path), bufferSize);
            BufferedOutputStream out = new BufferedOutputStream(
                new FileOutputStream(path + ".copy"), bufferSize)
        ) {
            byte[] buffer = new byte[bufferSize];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        }
        long elapsed = System.nanoTime() - start;
        System.out.printf("Buffer %d: %.2f ms%n", bufferSize, elapsed / 1_000_000.0);
    }
}

直接缓冲区 vs 堆缓冲区

对比堆缓冲区直接缓冲区
创建速度慢(需要 JNI)
读写性能一般高约 20%~50%
内存占用JVM 堆OS 内存
GC自动回收需手动释放
适合场景临时数据大文件、长期持有
java
// 堆缓冲区
ByteBuffer heap = ByteBuffer.allocate(8192);

// 直接缓冲区
ByteBuffer direct = ByteBuffer.allocateDirect(8192);

选型建议

┌─────────────────────────────────────────────────────────────────┐
│  选型口诀:                                                       │
│                                                                 │
│  日常开发用 8KB(默认)                                           │
│  大文件拷贝用 1MB~8MB                                             │
│  长期持有用 DirectBuffer                                          │
│  临时数据用 HeapBuffer                                            │
└─────────────────────────────────────────────────────────────────┘

基于 VitePress 构建