关键在减少、精准控制扩容,真正性能瓶颈是扩容时System.arraycopy()复制count个字符的O(n)操作;触发条件为count+新增字符数>capacity,扩容公式为newCapacity=oldCapacity×2+2。

优化 StringBuilder 的扩容成本,关键不在“避免扩容”,而在于让扩容更少、更准、更可控。真正拖慢性能的不是 append 本身,而是扩容那一刻触发的 System.arraycopy() —— 它要复制当前所有已存字符(count 个),时间复杂度 O(n),还会引发额外 GC。
扩容是怎么被触发的
StringBuilder 内部靠两个变量协同工作:
char[] value:底层数组,长度就是 capacity();
int count:当前已用字符数,也就是 length() 的返回值。
只要 count + 新增字符数 > value.length,下一次 append 就会强制扩容。
常见误区:以为 capacity 是“已用空间”,其实它是“已分配但未必填满”的总缓冲区大小。比如 new StringBuilder("abc"),length() 是 3,capacity() 却是 19(3 + 16)。
扩容公式和真实增长节奏
扩容不是线性加 16,也不是精确按需分配,而是分两步计算新容量:
- 先尝试倍增:
newCapacity = oldCapacity × 2 + 2(JDK 多数版本,位运算实现为value.length ) - 再兜底判断:如果这个值仍小于实际所需最小容量(即
count + 新增长度),就直接设为该最小容量
举例:
初始容量 16 → 追加 20 字符 → 触发扩容 → 计算得 34 ≥ 20 → 实际扩到 34;
若追加的是 50 字符 → 34
怎么预估并设置合理 capacity
预估目标不是“绝对精准”,而是留出弹性余量,兼顾内存与性能:
立即学习“Java免费学习笔记(深入)”;
- 静态场景(如拼接固定模板+几个变量):把模板长度 + 各变量最大可能长度加总,再 +10%~15% 余量
- 集合批量拼接(如
List<String>):遍历一次求和所有str.length(),再 +32 或向上取整到最近的 2 的幂(如 1000 → 1024,3000 → 4096) - 避免极端值:设成 10000 却只拼 200 字符,浪费堆内存,小对象 GC 反而变多
- 运行中动态补救:可用
ensureCapacity(expectedTotal)主动预留,它只在不足时生效,多次调用无副作用
其他有效降低开销的做法
- 复用实例:在方法内或
ThreadLocal中缓存 StringBuilder,避免反复 new + 初始化 16 字节数组 - 批量追加优于单字符:用
append(String)替代循环中多次append(char),减少边界检查和扩容判断次数 - 超大文本(MB 级)不硬扛:考虑
StringJoiner、BufferedWriter或流式处理,而非依赖单个 StringBuilder - 验证是否健康:运行时观察
capacity() - length(),长期接近 0 表示预留严重不足;若capacity()是length()的几十倍,说明预估过大

















