应使用预估容量的StringBuilder替代循环内+=拼接字符串,避免O(n²)性能陷阱;单线程用StringBuilder而非StringBuffer;toString()后可继续append,但日志中需显式调用toString()防止复用。

循环内用 += 拼接字符串就是性能杀手
每次 str += "x" 都会触发:新建 StringBuilder → append() → toString() → 丢弃旧 String。10 万次循环 = 10 万个中间 String 对象,GC 压力陡增,CPU 被拷贝和分配吃满。
这不是“有点慢”,而是典型 O(n²) 时间复杂度陷阱——尤其在日志收集、SQL 构建、词法分析器这类逐字符/逐字段累积场景下,延迟可能从毫秒级跳到秒级。
new StringBuilder(初始容量) 不是可选,是必填项
默认构造 new StringBuilder() 只分配 16 字符空间。一旦超出,就要扩容(通常是 oldCapacity * 2 + 2),触发 Arrays.copyOf() 数组拷贝。频繁扩容比多分配点内存更伤性能。
- 预估总长:比如拼接 500 个平均长度 30 的字符串,建议
new StringBuilder(15000) - 不确定时宁大勿小:设为 2048 或 4096,远好过反复扩容
- 别靠“先 new 再 setLength()”补救——
setLength()不改变底层数组大小,只是截断或补\0
单线程里用 StringBuffer 就是自废武功
StringBuffer 每个方法都带 synchronized,实测比 StringBuilder 慢 10%–15%。而绝大多数业务代码(Controller、Service、DAO 层)都是单线程执行上下文。
除非你明确把同一个 StringBuffer 实例跨线程共享并并发调用 append(),否则直接用 StringBuilder。IDEA/Sonar 的警告 "StringBuffer may be replaced with StringBuilder" 是性能红线,不是建议。
真有多线程需求?用 ThreadLocal<stringbuilder></stringbuilder>,或方法参数传入局部实例——别塞 static 字段。
toString() 后继续 append() 是无效操作
StringBuilder.toString() 返回的是一个全新 String,但原 StringBuilder 实例仍可继续用。问题在于:很多人调完 toString() 就以为“完成”,接着又 new StringBuilder() 重来,白白放弃已累积的内容。
更隐蔽的坑是日志场景:logger.debug("{}", sb) ——某些日志框架(如 Logback)会缓存 StringBuilder 引用或在其上反复调用 toString(),导致意外复用或状态污染。稳妥做法是 logger.debug("{}", sb.toString()),确保传出去的是纯不可变副本。
真正难处理的,从来不是“要不要换 StringBuilder”,而是容量预估是否合理、作用域是否收得干净、以及 toString() 的时机是否被日志或框架悄悄劫持。


















