预分配容量是 StringBuilder 性能优化最有效且低成本的方式;默认16容量常不足,扩容触发数组复制开销大,应根据模板、变量长度总和加余量估算并初始化。

预分配容量是 StringBuilder 性能优化里见效最快、成本最低的一招——不是“可选”,而是“该做就做”。默认 16 的初始容量在真实业务场景中几乎总是不够用,一次扩容触发的数组复制,就可能拖慢整个循环。
为什么预分配容量这么关键
StringBuilder 底层是个 char 数组。每次 append 内容时,如果当前长度 + 新增长度超过数组容量,就会触发扩容:new 一个更大的数组 → 把旧内容 copy 过去 → 丢弃旧数组。这个过程开销不小,尤其在高频拼接中:
- 扩容公式是 oldCap × 2 + 2,不是线性增长,容易造成多次复制(比如从 16 → 34 → 70 → 142…)
- 压测时发现 CPU 热点集中在 Arrays.copyOf,GC 次数明显上升,往往就是扩容太勤
- 实测:10 万次追加未预分配,平均触发 16 次复制;预估后设对容量,复制降为 0–1 次
怎么估算容量才靠谱
不用精确到个位,但要有基本数量级意识。关键是把所有待拼内容的长度加起来,再留点余量:
-
固定模板 + 变量:比如日志格式
"[{}][{}] {}", level, ts, msg,先算模板字符数(如 12),再加变量最大可能长度(level 最长 8,ts 约 23,msg 预估 512),总和 ≈ 555,可设new StringBuilder(600) -
集合拼接:100 个平均 20 字符的字符串,加逗号分隔,总长 ≈ 100×20 + 99 = 2099,设
new StringBuilder(2200)更稳妥 - 完全不确定但量大:宁可略高估(+10%~20% 或直接加 32~64),也别让扩容反复发生;设成 4096 比默认 16 强得多
哪些操作会悄悄破坏预分配效果
写了容量,不代表就稳了。几个常见坑要注意:
立即学习“Java免费学习笔记(深入)”;
-
混用 + 和 append:比如
sb.append("a" + "b"),编译器会在内部新建临时 StringBuilder 处理字面量拼接,白费预分配 -
循环里反复 new:每次迭代都
new StringBuilder(),预分配再准也没用;必须循环外创建、循环内复用 - 调用 setLength(0) 后不重估:它只清逻辑长度,底层数组没变;但如果后续拼的内容比原数组还长,仍会扩容
- 误用 StringBuffer 替代:扩容行为一样,但每个方法都带 synchronized,单线程下性能反而更低
附:一个简洁实用的初始化建议
日常开发中,按场景选一个起点即可:
- 简单日志或参数拼接 →
new StringBuilder(256) - 中等规模数据组装(几十~几百项)→
new StringBuilder(2048) - 批量导出、大文本生成 → 先粗算总量,再加 10% 余量
不复杂但容易忽略。



















