单线程场景下必须用 StringBuilder 替代 StringBuffer 以提升 10%–15% 吞吐量;需确保实例不被多线程共享,预设足够容量避免扩容开销,并在日志等场景显式调用 toString() 防止状态污染。

单线程场景下,直接用 StringBuilder 替代 StringBuffer 就能立竿见影地提升性能——不是“可以换”,而是“必须换”。关键不在语法迁移,而在理解为什么换、怎么换得干净、以及哪些细节会悄悄拖后腿。
确认是单线程场景再动手
绝大多数业务代码天然运行在单线程上下文中:Spring MVC 的 Controller 方法、MyBatis 的 DAO 执行、本地日志组装、JSON 字段拼接等。只要 StringBuilder 实例不被多个线程共享(比如没放在 static 字段里、没作为参数跨线程传递),就属于安全替换范围。
- Web 请求处理中每个请求独享一个 StringBuilder 实例 → 可换
- 工具类里定义了 private static StringBuffer 用于全局计数 → 不可直接换,需改用 ThreadLocal<StringBuilder> 或重构作用域
- 异步任务中把同一个 StringBuilder 传给多个 CompletableFuture → 不可换,应改为每个任务新建或加锁
替换不只是改类名,还要去掉同步包袱
StringBuffer 的每个 public 方法(append、insert、delete、toString)都加了 synchronized,而 StringBuilder 完全没有。这个差异不是“少点字节”,而是实测 10%–15% 的吞吐提升,尤其在高频 append 场景下更明显。
- 旧代码:
StringBuffer sb = new StringBuffer(); sb.append("a").append("b"); - 新写法:
StringBuilder sb = new StringBuilder(); sb.append("a").append("b"); - IDEA 或 SonarQube 报出 “StringBuffer may be replaced with StringBuilder” 时,别当成警告忽略——这是明确的性能红线
预设容量比换类更重要
默认构造 new StringBuilder() 只分配 16 字符空间。一旦超出,就要扩容(通常是 oldCapacity × 2 + 2),触发数组拷贝。频繁扩容比多分配点内存伤得多。
立即学习“Java免费学习笔记(深入)”;
- 拼接 300 个平均长度 20 的字符串 → 预估总长 6000,写
new StringBuilder(6000) - 不确定长度但量级可控(如日志行、SQL 参数列表)→ 宁设为 2048 或 4096,别依赖默认值
- 避免事后补救:
sb.setLength(0)不重置底层数组大小,只是逻辑清空;真正要复用,应搭配sb.setLength(0)+ 预设容量初始化
toString() 后别误以为“结束”,也别让日志框架偷偷复用
sb.toString() 返回的是新 String,但 StringBuilder 本身还能继续 append。常见误区是调完 toString 就丢弃,下轮又 new 一个,白白浪费已累积内容。
- 循环内拼接 → 每次复用同一实例,最后 toString 一次即可
- 日志输出:
logger.debug("result: {}", sb)在 Logback 等框架中可能缓存引用或反复调用 toString(),导致状态污染 → 改为logger.debug("result: {}", sb.toString()) - 返回前确保调用 toString(),别直接 return sb(类型不匹配且易引发后续误用)


















