bigywater.tech
Java 内部原理 · 114

StringBuilder 扩容策略真实验证:oldCapacity*2+2 公式真实逐轮验证

"StringBuilder扩容大约是翻倍"是个模糊的印象,这篇真实用反射直接读取内部数组长度,把整个扩容曲线逐轮精确验证出来,连公式里那个容易被忽视的"+2"都真实对上了。

默认初始容量真实是16
真实验证:new StringBuilder()的初始容量真实是16,new StringBuilder("hello")真实是字符串长度5加16等于21
扩容公式真实精确验证5轮
真实测出完整容量曲线[1,50,102,206,206,414],每一轮都真实精确符合 oldCapacity*2+2 这条公式

上一篇:Map.merge() 真实验证

真实验证:初始容量规则

用反射直接读取StringBuilder内部数组的真实长度,验证不同构造方式的初始容量。

StringBuilderCapacityDemo.java Field valueField = StringBuilder.class.getSuperclass().getDeclaredField("value"); valueField.setAccessible(true); // value 真实是JDK9+紧凑字符串的byte[]
真实实测
new StringBuilder() 真实初始容量: 16
new StringBuilder(50) 真实初始容量: 50
new StringBuilder("hello") 真实初始容量: 21(字符串长度5 + 16)

真实结果验证了三种构造方式的初始容量规则:无参构造真实固定分配16;显式指定容量的构造函数真实按传入值分配;传入初始字符串的构造函数真实按"字符串长度 + 16"分配——多出来的16是给后续追加操作预留的缓冲空间,避免刚构造完就因为追加一两个字符触发一次扩容。

真实验证:扩容公式 oldCapacity*2+2,连续5轮精确验证

从容量1开始,每轮追加50个字符,连续5轮,记录真实的容量增长曲线。

StringBuilderCapacityDemo.java StringBuilder sb5 = new StringBuilder(1); for (int i = 1; i <= 5; i++) { sb5.append("x".repeat(50)); // 每次真实追加50个字符 capacities[i] = getCapacity(sb5); }
真实实测
真实容量增长曲线: [1, 50, 102, 206, 206, 414]
逐轮对上公式的真实证据。把这条曲线拆开逐轮验证:第1轮容量从1变成50(需要长度50 > 1*2+2=4,所以真实直接扩到刚好需要的50);第2轮已有内容50,再加50总需求100,oldCapacity=50,公式50*2+2=102,需求100<102,真实扩到102;第3轮总需求150,oldCapacity=102,公式102*2+2=206,需求150<206,真实扩到206;第4轮总需求200,此时现有容量206已经够用(200≤206),真实完全不触发扩容,容量保持206不变;第5轮总需求250,oldCapacity=206,公式206*2+2=414,需求250<414,真实扩到414。五轮数据真实精确吻合 newCapacity = max(minimumCapacity, oldCapacity*2+2) 这条公式,包括容易被忽略的"+2"和"第4轮完全不扩容"这个细节。

真实验证:一次性追加远超公式计算值的内容

如果一次append()需要的空间,比oldCapacity*2+2算出来的还大很多,会怎样?

真实实测
容量4的StringBuilder真实一次性追加1000字符后容量: 1000(真实直接扩到>=1000,不是4*2+2=10)

真实结果证明扩容公式真实是 max(所需最小容量, oldCapacity*2+2) 取两者较大值——当一次性追加的内容远超"翻倍"能提供的空间时(这里公式算出来只有10,但真实需要1000),StringBuilder 会真实直接扩到刚好能装下这次内容的大小,不会先扩到10再继续扩容好几轮。

真实验证:trimToSize() 能真实收缩多余容量

真实实测
trimToSize前真实容量: 1000
trimToSize后真实容量: 5(真实收缩到刚好装下内容的长度)

真实结果验证了 trimToSize() 这个不太常用但很实用的方法——把内部数组真实收缩到恰好等于当前内容长度,释放掉扩容过程中积累的冗余空间,适合在"确定不会再有大量追加,但要长期持有这个StringBuilder"的场景下调用,减少内存占用。

分步演示:StringBuilder 的 capacity() 是怎么真实一步步变化的

每一行都是真实运行这段代码得到的结果。

StringBuilder 默认容量16,扩容公式真实是 oldCapacity*2+2;公式不够用时真实按精确需求分配,setLength 缩短不会真实收缩容量
这一步新验证的容量

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到(--add-opens java.base/java.lang=ALL-UNNAMED),扩容曲线重复运行完全一致,这是确定性算法,不涉及随机因素。
    • 和数组 vs ArrayList 性能那篇一起看,能理解ArrayList的1.5倍扩容策略和StringBuilder的2倍+2扩容策略这两种不同增长曲线背后的设计考量差异。
    • 没有涉及:ArrayList的真实扩容公式(newCapacity = oldCapacity + oldCapacity/2,即1.5倍)与StringBuilder的2倍策略对比、JDK9+紧凑字符串(Latin1用byte[],非Latin1用char[])对StringBuilder内部存储和扩容行为的真实影响——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号