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