应拼接前预估总长度并一次性调用ensureCapacity或使用带参构造器,避免循环中多次扩容;复用时用setLength(0);需通过JFR、GC日志及容量利用率验证效果。

直接在拼接开始前调用 ensureCapacity,预估最大长度并一次性预留足够空间,就能避开多次数组复制——关键不是“要不要调”,而是“什么时候调、调多少、怎么确认它真起效了”。
预估总长度再一次性调用
不要在循环里边拼边判断容量,那样既增加无效方法调用,又无法规避扩容逻辑(append 内部已自带扩容判断)。正确做法是:拼接前统计算法能覆盖的最大字符数,加上 5%~10% 余量(应付分隔符、换行、字段长度波动等),然后只调一次 ensureCapacity。
- 比如拼接 200 个平均 15 字符的字符串,加逗号分隔:200 × 15 + 199 ≈ 3199,可取 3400 或向上取整到最接近的 2 的幂(如 4096)
- 解析 CSV 行前,先估算该行最大可能长度(含所有字段+分隔符+换行符),再调
sb.ensureCapacity(lineEstimate) - 批量生成 JSON,字段数固定为 1000,每个键值对平均占 20 字符,总长预估 20000,预留 22000 即可
优先用带参构造器,而非事后补救
如果创建时就能预估长度,直接用 new StringBuilder(int capacity) 更简洁高效。相比先 new 再 ensureCapacity,少一次方法调用,也避免实例已部分使用后才补容量的冗余操作。
- 默认初始容量是 16,拼接超长内容时极易触发多次扩容(16→34→70→142…),每次都要
System.arraycopy - 构造时指定容量,底层 char[] 一次分配到位,全程零拷贝
- 注意别设得过大——比如只拼 300 字符却设成 10000,会浪费堆内存,反而加重 GC 压力
复用实例时用 setLength(0),保留已有 capacity
高频场景下(如日志收集、模板渲染),别每次 new StringBuilder;改用 ThreadLocal 缓存或对象池,重置时调 setLength(0),它不清空底层数组,也不触发 GC,下次 append 仍可复用已有容量。
立即学习“Java免费学习笔记(深入)”;
-
setLength(0)比new StringBuilder()轻量得多,适合线程内反复使用 - 若复用实例后续拼接内容明显变长,可在重置后重新调
ensureCapacity预留新空间 - 避免在工具方法中隐式扩容——调用方传入前确保容量充足,能让组合逻辑更稳定
验证扩容是否真正被绕过
别光靠“写了 ensureCapacity 就安心”。实际效果要靠运行时指标验证:
- 用 JFR 或 VisualVM 观察
System.arraycopy调用次数是否显著下降 - 监控 GC 日志,看是否减少因频繁分配小 char[] 引发的 Young GC 次数
- 打印
sb.capacity()和sb.length(),确认最终容量利用率在 70%~90% 区间(过低说明预估偏高,过高可能仍有意外扩容)


















