1. 概述
本教程将深入探讨 Java 中两个基础文件写入类的性能差异:FileWriter 和 BufferedWriter。尽管网上普遍认为 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 图:
关键点:
- ✅ 两者都继承自 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 基准测试,我们得出以下结论:
默认缓冲区机制:
- JDK10-18:两者默认缓冲区均为 8192 字节
- 更高版本:512-8192 字节可变范围
性能表现:
- ✅ 所有写入场景(单次/多次)性能差异可忽略不计
- ❌ 增大 BufferedWriter 缓冲区无实际收益
实践建议:
- 简单场景直接使用 FileWriter 即可
- BufferedWriter 的主要价值在于提供额外 API(如
newLine()
) - ⚠️ 网上关于"BufferedWriter 必然更快"的说法属于过时认知
完整源代码见 GitHub 仓库。