stringbuffer扩容在“要放不下时”立即触发,依据是当前字符数加待追加长度是否超底层数组长度;新容量默认为旧长×2+2,但若需容量更大则直接设为目标值;优化关键在于预估并设置合理初始容量。

StringBuffer的扩容不是“用完了才扩”,而是“要放不下就扩”——关键看 当前已存字符数 + 即将追加内容长度 是否超过底层数组当前长度(value.length)。一旦越界,立即触发扩容,整个过程本质是数组复制,开销不小。
扩容触发条件很明确
判断依据不是“用了多少”,而是“总共要放多少”。例如:
- 初始容量16,调用
append("abcdefghijklmnopqrstuvwxyz")(26字符)→ 0+26 > 16 → 立即扩容 - 同样初始容量16,分14次每次
append("a")→ 前15次都不触发(count=15时,15+1=16,刚好不超);第16次再append一个字符 → 16+1=17 > 16 → 触发扩容
新容量怎么算:2×旧长+2,但不硬套
公式是:新容量 = 旧数组长度 × 2 + 2,但它只是默认策略,实际会兜底:
- 16 → 16×2+2 = 34
- 34 → 34×2+2 = 70
- 70 → 70×2+2 = 142
- 但如果当前容量34,却要一次性追加200字符(34+200=234),那么新容量直接设为234,跳过倍增逻辑
这个“+2”设计是为了防止小容量(如1、2)首次扩容后仍不够用,也避免传入0导致非法数组。
扩容本质是三次内存操作
每次扩容都不是简单“变大”,而是完整替换:
- 分配一块新
char[](JDK9+可能是byte[]),大小按上述规则确定 - 调用
Arrays.copyOf(value, newCapacity)把旧内容逐字拷贝过去 - 将内部
value引用指向新数组,原数组等待GC回收
压测中若发现 Arrays.copyOf 是CPU热点,基本就是扩容太频繁了。
最有效的优化:预分配合理初始容量
默认16对多数业务偏小,盲目设大又浪费内存。关键是结合场景估算:
- 静态模板拼接(如日志格式):模板本身长度 + 各变量最大可能长度(例:用户ID≤32 + 订单号≤64 + 其他字段)
- 动态数据拼接(如CSV行生成):单行平均长度 × 1.2,或直接加固定余量(如+64)
-
循环内复用同一实例:在循环外用
new StringBuffer(256)初始化一次,后续全程零扩容
设5000却只拼200字符,不仅浪费堆内存,还会让GC更吃力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











