stringbuilder扩容在count+新增长度>value.length时触发,按old*2+2计算新容量,与stringbuffer机制一致但无同步开销。

StringBuilder 的容量调整不是“自动智能”,而是有明确触发条件和固定计算逻辑的底层行为。预估不准或完全放任,默认扩容会带来数组复制开销、GC压力上升,甚至影响年轻代晋升节奏。关键不在“要不要调”,而在于“何时调、调多少、怎么验证”。
扩容什么时候真正发生?
扩容只在内部缓冲区即将“装不下”时触发,判断依据是:当前已用长度(count) + 本次要追加/插入的字符数 > 当前底层数组长度(value.length)。不是按 append 次数,也不是按字符串对象数量,而是纯粹看字符总字节数(JDK9+ 下对 Latin-1 字符仍按 byte 计,但逻辑长度一致)。
- 调用
append("abc")时,若count + 3 > value.length,立即扩容 - 调用
insert(5, "xyz")时,若5 + 3 > value.length,也会触发(注意:插入位置影响判断) -
setLength(0)或delete(0, count)只重置count,不释放数组;下次 append 仍可能扩容
初始容量与 ensureCapacity() 怎么选才合理?
默认构造器 new StringBuilder() 给 16,适合拼接几个单词;一旦超过,就得走扩容流程。预分配不是越大越好,而是讲策略:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 可预估总量时:取所有待拼内容长度之和 × 1.2~1.5,再向上取整到最接近的 2 的幂(如 1800 → 2048,3500 → 4096)
-
循环拼接 N 条记录时:每条平均长度 L,直接设
new StringBuilder(N * L + 64)(+64 是留出分隔符、换行等余量) -
动态增长但有上限时:在关键节点(如循环开始前、解析完头部后)调用
sb.ensureCapacity(expectedMax),避免循环内反复判断 -
千万别在 for 循环里写
sb.ensureCapacity(sb.length() + len)—— 方法调用本身就有开销,且几乎每次都不生效
怎么知道扩容到底发生了几次?
capacity() 是只读视图,不能直接反映扩容次数。真实扩容行为需结合运行时观测:
- 用 JVM 参数
-XX:+PrintGCDetails观察 young GC 频率突增,间接提示大量数组拷贝压力 - 启用 JFR(Java Flight Recorder),录制期间筛选
jdk.ArrayCopy事件,统计调用栈中是否高频出现StringBuilder.ensureCapacityInternal - 简单压测对比:同一逻辑,分别用
new StringBuilder(4096)和new StringBuilder()运行,看耗时与 GC 时间差值 —— 差距明显说明扩容成了瓶颈
StringBuffer 和 StringBuilder 在容量上真没区别?
底层扩容机制完全一致:初始容量都是 16,扩容公式都是 old * 2 + 2,ensureCapacity() 行为一模一样。唯一差别是 StringBuffer 所有 public 方法都加了 synchronized,导致单线程下性能低 2–3 倍。多线程场景下,与其共享一个 StringBuffer,不如每个线程配一个预分配好的 StringBuilder —— 更轻量、更可控、GC 更友好。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










