因为string不可变,循环中用+拼接会每次新建对象,10万次迭代产生10万个临时对象,引发频繁gc;jdk 8+仅优化单行多个+,跨循环迭代的+无法优化。

为什么循环里用 + 拼接字符串会变慢
Java 中每次用 + 拼接字符串,底层都会新建一个 String 对象(因为 String 不可变),哪怕只拼两个短字符串。在大循环中,比如 10 万次迭代,就会产生 10 万个中间 String 对象,频繁触发 GC,CPU 花在对象分配和回收上的时间远超实际拼接逻辑。
这问题在 JDK 8+ 编译器做了部分优化(比如把同一行的多个 + 合并为 StringBuilder),但跨循环迭代的 + 完全不优化——编译器无法跨迭代做逃逸分析,所以你写的:
String result = ""; for (int i = 0; i <p>等价于每次迭代都 new 一个 <code>StringBuilder</code>、append、toString,开销翻倍。</p><h3>StringBuilder.append() 的正确用法和初始化技巧</h3><p>直接复用同一个 <code>StringBuilder</code> 实例是关键。但光用 <code>append()</code> 还不够,得配合合理初始化容量,避免内部数组反复扩容:</p>
- 默认构造函数创建的
StringBuilder初始容量是 16,一旦超过就扩容为oldCapacity * 2 + 2,涉及数组复制 - 如果能预估最终长度(比如每项约 10 字符 × 10 万次 ≈ 100 万字符),就显式指定容量:
new StringBuilder(1_000_000) - 若无法精确估算,也建议给个保守下限(如
new StringBuilder(64 * 1024)),比默认 16 更稳妥
StringBuilder sb = new StringBuilder(500_000); for (int i = 0; i <h3>append() 链式调用与参数类型的影响</h3><p><code>StringBuilder.append()</code> 是重载方法,不同参数类型性能略有差异:</p>
-
append(String)和append(char)直接拷贝引用或单字符,最快 -
append(int)、append(long)等数字类型会走内部字符数组转换,比先String.valueOf()再append(String)略快(少一次对象创建) - 避免
append(obj.toString()),直接append(obj)即可——StringBuilder有专门的append(Object)重载,会安全调用String.valueOf()
常见错误写法:
- 在循环内反复
sb.toString()—— 每次都生成新String,失去复用意义 - 用
sb.append("\n")代替sb.append(System.lineSeparator())—— 前者硬编码换行符,跨平台可能出错 - 忽略线程安全:若多线程共用一个
StringBuilder,必须加锁或改用StringBuffer(但通常应避免共享)
什么时候 StringBuilder 反而更慢
不是所有场景都适合提前上 StringBuilder:
- 循环次数极少(比如 + 优化掉,强行用
StringBuilder反而增加代码复杂度和对象创建开销 - 拼接内容本身来自 IO 或网络(如读文件逐行拼接),瓶颈在 IO,字符串操作占比极小,优化无意义
- 使用了
Stream.collect(Collectors.joining())等现代 API:它内部就是用StringBuilder实现的,且做了容量预估,比手写更可靠
真正要盯住的是:循环体是否纯 CPU 密集型字符串操作 + 迭代数 > 1000。其他情况先测再改,别凭感觉过早优化。
字符串拼接的坑往往不在语法,而在对象生命周期和容量预期——很多人写了 StringBuilder 却忘了初始化容量,结果只是把“新建 String”换成了“频繁扩容 StringBuilder”,性能提升微乎其微。










