stringbuffer和stringbuilder扩容逻辑统一由abstractstringbuilder的ensurecapacityinternal和expandcapacity方法实现,触发条件是count+新增长度>value.length,新容量按oldlength×2+2计算但不低于mincapacity,扩容时全量复制char[]并替换引用,优化需预估初始容量避免频繁复制。

直接看 AbstractStringBuilder 的 ensureCapacityInternal 和 expandCapacity 方法,这是 StringBuffer(和 StringBuilder)扩容逻辑的统一入口。StringBuffer 本身不实现扩容,它完全继承自父类,只在方法上加了 synchronized。
扩容触发时机:不是“用完才扩”,而是“要放不下就扩”
判断依据非常明确:当前已存字符数(count) + 即将追加内容长度是否 > 底层数组当前长度(value.length)。只要越界,立刻扩容。
- 例如:初始容量 16,调用
append("0123456789abcdef")(16 字符),count=16,此时再 append 一个字符 →16 + 1 = 17 > 16→ 触发扩容 - 注意:
count是实际字符数,value.length是底层数组总长;两者不相等时,数组有空余但尚未填满,不会提前扩
新容量怎么算:倍增+兜底,不是死公式
核心逻辑在 newCapacity(int minCapacity) 方法里:
- 先按默认策略计算:
newCapacity = oldLength × 2 + 2(等价于(oldLength ) - 再比对:如果这个值仍小于所需最小容量(
minCapacity),就直接取minCapacity - 最后做溢出检查,超限则设为
Integer.MAX_VALUE
也就是说,扩容不是机械套公式——一次性追加 200 字符导致需 216 容量,而当前是 34,那么新容量就是 216,跳过 70、142 这些中间值。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
内存分配本质:一次复制,两次对象生命周期切换
每次扩容都包含三步不可省略的操作:
- 分配一块新的
char[](JDK 9+ 可能是byte[]),大小按上述规则确定 - 调用
Arrays.copyOf(value, newCapacity),逐字符拷贝旧内容(不是部分拷贝,是全量复制) - 将内部
value引用指向新数组,原数组失去引用,等待 GC 回收
这就是为什么压测中 Arrays.copyOf 出现在 CPU 火焰图顶部——说明扩容太频繁,复制成了瓶颈。
优化关键:预估初始容量,避免盲目构造
无参构造默认 16,对多数业务偏小。真正有效的做法是结合场景预估:
- 静态模板拼接(如日志格式):模板长度 + 所有变量最大可能长度之和(例:用户 ID ≤32 + 时间戳 ≤19 + 状态码 ≤5)
- 动态生成(如 CSV 行):单行平均长度 × 1.2,或直接加固定余量(如 +64)
- 循环内复用:在循环外初始化
new StringBuffer(256),后续全程不扩容 - 避免过度预留:设 5000 却只拼 200 字符,既浪费堆内存,又加重 GC 压力










