bufferedwriter 的 write(int c) 不自动刷新缓冲区,但高频单字符写入会因频繁检查空间、触发满刷、增加系统调用而严重降低性能;应合并写入或预分配大缓冲区。

Java 中 BufferedWriter 的 write(int c) 方法写单字符时,**默认不会触发缓冲区刷新,但频繁调用会显著降低缓存效率,甚至退化为逐字节/字符系统调用**。关键在于:缓冲的本质是减少底层 I/O 次数,而单字符写入极易绕过缓冲设计初衷。
缓冲区机制与 write(int c) 的实际行为
BufferedWriter 内部维护一个字符数组缓冲区(默认大小 8192 字符)。调用 write(int c) 时:
- 若缓冲区未满,该字符被直接写入缓冲区数组(内存操作,极快);
- 若缓冲区已满或剩余空间不足,会先将当前缓冲区内容 flush 到底层
Writer(如FileWriter),再把新字符写入缓冲区起始位置; -
它从不自动 flush——除非显式调用
flush()或close(),或缓冲区满、遇到换行符(仅对newLine()有特殊处理)。
为什么单字符写入容易导致性能问题
问题不在于单次调用慢,而在于**高频小写入打破缓冲节奏**:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 每写 1 字符就检查缓冲区剩余空间,增加分支判断开销;
- 若字符写入恰逢缓冲区临界点(例如只剩 1 空位时写入),会引发“写-满-刷-清-再写”循环,实际变成每次写都 flush;
- 底层
Writer(如OutputStreamWriter→FileOutputStream)可能被迫执行多次小块系统调用,丧失批量写入优势; - 极端情况(如循环中
for (char c : chars) bw.write(c);)性能可能比直接用FileWriter还差。
如何验证是否发生低效刷写
可通过以下方式定位瓶颈:
-
启用 JVM 系统监控:运行时加参数
-Dsun.stdout.verbose=true(非标准,需自定义包装),更可靠的是用java.lang.instrument或 JFR(JDK Flight Recorder)捕获FileOutputStream.write(byte[],...)调用频次与 size 分布; -
替换为带日志的装饰器:包装底层
Writer,统计write(char[], off, len)被调用次数和每次 len 值,若大量出现len == 1,说明缓冲失效; -
对比基准测试:对同一数据,分别用
BufferedWriter.write(int)单字符循环 vsBufferedWriter.write(String)批量写,测量耗时差异(通常相差 5–50 倍)。
高效写入单字符的实用建议
不是禁止单字符写,而是避免无意识滥用:
- 能合并就合并:用
String.valueOf(c)或 StringBuilder 累积后再write(String); - 明确控制缓冲节奏:对已知长度字符序列,预分配足够缓冲区(
new BufferedWriter(writer, 65536))并配合手动flush(); - 区分场景:日志类少量写入影响不大;生成文本文件、序列化等大批量场景必须避免单字符循环;
- 必要时绕过 BufferedWriter:若业务逻辑天然逐字符(如状态机输出),考虑用
CharArrayWriter先收集,最后一次性 dump 到BufferedWriter。
不复杂但容易忽略:BufferedWriter 的“缓冲”二字,只对批量写入有效;把它当字符级 API 用,等于拿卡车运芝麻。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










