StringBuilder通过复用内部可变字符数组避免频繁创建对象:所有操作原地修改,仅最后toString()生成一个String实例;而String不可变,每次拼接都新建对象,导致大量临时对象和GC压力。

StringBuilder 通过复用内部可变字符数组,从根源上避免了字符串频繁创建对象的问题。它不依赖 String 的不可变语义,所有拼接、插入、删除操作都在同一个底层数组上原地进行,直到最后调用 toString() 才生成唯一一个 String 实例。
为什么 String 拼接会不断新建对象
String 是不可变的,底层用 final byte[](JDK 9+)或 final char[] 存储内容。任何“修改”——比如 +、concat()、substring()——都必须创建新对象:
- 每次
s += "x"实际等价于new StringBuilder().append(s).append("x").toString() - 循环中执行 10 万次,就会产生约 10 万个中间 String 和 StringBuilder 临时对象
- 这些对象短命、堆内存瞬时飙升,触发频繁 Minor GC,甚至导致老年代提前晋升
StringBuilder 是怎么做到复用的
它继承自 AbstractStringBuilder,持有可变的 byte[] value(JDK 9+)或 char[] value(JDK 8-),所有操作直接修改该数组:
- 容量足够时:仅更新内部
count计数器,不分配新内存 - 容量不足时:一次性扩容(通常为
oldCapacity * 2 + 2),复制原有内容,仍只新建一次数组 - 全程不生成任何 String 对象,
toString()仅在最终调用时构建一个结果实例
正确用法才能真正避免对象创建
光用 StringBuilder 不够,写法不当仍会引入额外对象:
立即学习“Java免费学习笔记(深入)”;
- 初始化时预估容量,如
new StringBuilder(2048),减少扩容次数 - 不要在循环内反复调用
toString(),否则每轮都生成新 String - 避免在多线程间共享同一实例;需线程安全时,优先用
ThreadLocal<stringbuilder></stringbuilder> - 循环外创建实例,循环内重用:
sb.setLength(0)清空比新建更轻量
什么场景下没必要用 StringBuilder
不是所有拼接都需要手动干预:
- 编译期常量拼接(如
"a" + "b" + "c")会被 JVM 直接优化为字面量 - 简单变量拼接(如
"id=" + id)多数 JVM 也会自动转为 StringBuilder 调用 - 单次、低频拼接,手动用 StringBuilder 反而增加代码复杂度,无实际收益


















