1. 概述

本教程将深入探讨 Java 中两个基础文件写入类的性能差异:FileWriterBufferedWriter。尽管网上普遍认为 BufferedWriter 性能更优,但我们将通过实际测试验证这一假设。

在分析基本用法、继承关系和底层实现后,我们将使用 Java Microbenchmark Harness (JMH) 进行性能测试。测试环境为 Linux + JDK17,但结论同样适用于其他操作系统和较新 JDK 版本。

2. 基本用法

FileWriter 使用默认缓冲区写入文本文件,但缓冲区大小在Javadoc中未明确说明:

FileWriter writer = new FileWriter("testFile.txt");
writer.write("Hello, Baeldung!");
writer.close();

BufferedWriter 是另一种选择,它包装其他 Writer 类(包括 FileWriter):

int BUFSIZE = 4194304; // 4MiB
BufferedWriter writer = new BufferedWriter(new FileWriter("testBufferedFile.txt"), BUFSIZE);
writer.write("Hello, Buffered Baeldung!");
writer.close();

这里我们指定了 4MiB 缓冲区。若未指定大小,默认缓冲区大小同样在Javadoc中未定义。

3. 继承关系

以下是 FileWriter 和 BufferedWriter 的继承结构 UML 图:

FileWrite and BufferedWriter Hierarchy

关键点:

  • ✅ 两者都继承自 Writer
  • ✅ FileWriter 基于 OutputStreamWriter 实现
  • ❌ 继承关系和文档均未揭示默认缓冲区大小

4. 底层实现

FileWriter 的默认缓冲区大小

  • JDK10-18:固定 8192 字节
  • 更高版本:512-8192 字节可变

FileWriter 继承 OutputStreamWriter,后者使用内部类 StreamEncoder(位于 sun.nio.cs 包)。其源码包含:

  • JDK18 前:DEFAULT_BYTE_BUFFER_SIZE = 8192
  • 更高版本:MAX_BYTE_BUFFER_CAPACITY = 8192

BufferedWriter 的默认缓冲区大小

  • 与 FileWriter 完全相同
  • JDK10-18:defaultCharBufferSize = 8192
  • 更高版本:DEFAULT_MAX_BUFFER_SIZE = 8192
  • ✅ 支持自定义缓冲区大小(如示例中的 4MiB)

⚠️ 尽管两者都有缓冲机制,但 FileWriter 因文档过时仍被误认为"无缓冲"。

5. 性能对比

5.1. 磁盘写入同步

为获得准确基准测试结果,必须禁用操作系统缓存

  • Linux:使用 sync 选项重新挂载文件系统
    $ sudo mount -o remount,sync /path/to/mount
    
  • macOS:mount 命令支持 sync 选项
  • Windows:设备管理器 → 磁盘属性 → 策略 → 禁用"启用设备上的写入缓存"

❌ 未同步的异步写入会严重干扰测试准确性

5.2. 测试方案

我们使用 JMH 测试不同写入场景(单次/10次/1000次/10000次/100000次):

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class BenchmarkWriters {
    private static final Logger log = LoggerFactory.getLogger(BenchmarkWriters.class);
    private static final String FILE_PATH = "benchmark.txt";
    private static final String CONTENT = "This is a test line.";
    private static final int BUFSIZE = 4194304; // 4MiB

    @Benchmark
    public void fileWriter1Write() {
        try (FileWriter writer = new FileWriter(FILE_PATH, true)) {
            writer.write(CONTENT);
            writer.close();
        } catch (IOException e) {
            log.error("Error in FileWriter 1 write", e);
        }
    }

    @Benchmark
    public void bufferedWriter1Write() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH, true), BUFSIZE)) {
            writer.write(CONTENT);
            writer.close();
        } catch (IOException e) {
            log.error("Error in BufferedWriter 1 write", e);
        }
    }

    @Benchmark
    public void fileWriter10Writes() {
        try (FileWriter writer = new FileWriter(FILE_PATH, true)) {
            for (int i = 0; i < 10; i++) {
                writer.write(CONTENT);
            }
            writer.close();
        } catch (IOException e) {
            log.error("Error in FileWriter 10 writes", e);
        }
    }

    @Benchmark
    public void bufferedWriter10Writes() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH, true), BUFSIZE)) {
            for (int i = 0; i < 10; i++) {
                writer.write(CONTENT);
            }
            writer.close();
        } catch (IOException e) {
            log.error("Error in BufferedWriter 10 writes", e);
        }
    }

    // 其他测试方法(1000/10000/100000次写入)结构类似...
}

关键配置:

  • ✅ 每个测试独立打开/关闭文件
  • @Fork(1) 确保单线程顺序执行
  • ✅ 主线程执行所有写入操作

5.3. 测试结果

使用 4MiB 缓冲区的 BufferedWriter

Benchmark                                    Mode  Cnt     Score     Error  Units
BenchmarkWriters.bufferedWriter100000Writes  avgt   10  9170.583 ± 245.916  ms/op
BenchmarkWriters.bufferedWriter10000Writes   avgt   10   918.662 ±  15.105  ms/op
BenchmarkWriters.bufferedWriter1000Writes    avgt   10   114.261 ±   2.966  ms/op
BenchmarkWriters.bufferedWriter10Writes      avgt   10    37.999 ±   1.571  ms/op
BenchmarkWriters.bufferedWriter1Write        avgt   10    37.968 ±   2.219  ms/op
BenchmarkWriters.fileWriter100000Writes      avgt   10  9253.935 ± 261.032  ms/op
BenchmarkWriters.fileWriter10000Writes       avgt   10   951.684 ±  41.391  ms/op
BenchmarkWriters.fileWriter1000Writes        avgt   10   114.610 ±   4.366  ms/op
BenchmarkWriters.fileWriter10Writes          avgt   10    37.761 ±   1.836  ms/op
BenchmarkWriters.fileWriter1Write            avgt   10    37.912 ±   2.080  ms/op

使用默认缓冲区的 BufferedWriter

Benchmark                                    Mode  Cnt     Score     Error  Units
BenchmarkWriters.bufferedWriter100000Writes  avgt   10  9117.021 ± 143.096  ms/op
BenchmarkWriters.bufferedWriter10000Writes   avgt   10   931.994 ±  34.986  ms/op
BenchmarkWriters.bufferedWriter1000Writes    avgt   10   113.186 ±   2.076  ms/op
BenchmarkWriters.bufferedWriter10Writes      avgt   10    40.038 ±   2.042  ms/op
BenchmarkWriters.bufferedWriter1Write        avgt   10    38.891 ±   0.684  ms/op
BenchmarkWriters.fileWriter100000Writes      avgt   10  9261.613 ± 305.692  ms/op
BenchmarkWriters.fileWriter10000Writes       avgt   10   932.001 ±  26.676  ms/op
BenchmarkWriters.fileWriter1000Writes        avgt   10   114.209 ±   5.988  ms/op
BenchmarkWriters.fileWriter10Writes          avgt   10    38.205 ±   1.361  ms/op
BenchmarkWriters.fileWriter1Write            avgt   10    37.490 ±   2.137  ms/op

核心发现

  • ✅ 所有测试场景下两者性能几乎完全一致
  • ❌ 增大 BufferedWriter 缓冲区(4MiB)未带来性能提升

6. 结论

通过 JMH 基准测试,我们得出以下结论:

  1. 默认缓冲区机制

    • JDK10-18:两者默认缓冲区均为 8192 字节
    • 更高版本:512-8192 字节可变范围
  2. 性能表现

    • ✅ 所有写入场景(单次/多次)性能差异可忽略不计
    • ❌ 增大 BufferedWriter 缓冲区无实际收益
  3. 实践建议

    • 简单场景直接使用 FileWriter 即可
    • BufferedWriter 的主要价值在于提供额外 API(如 newLine()
    • ⚠️ 网上关于"BufferedWriter 必然更快"的说法属于过时认知

完整源代码见 GitHub 仓库


原始标题:Guide to FileWriter vs. BufferedWriter | Baeldung