"StringBuilder扩容大约是翻倍"是个模糊的印象,这篇真实用反射直接读取内部数组长度,把整个扩容曲线逐轮精确验证出来,连公式里那个容易被忽视的"+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是给后续追加操作预留的缓冲空间,避免刚构造完就因为追加一两个字符触发一次扩容。
从容量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]
如果一次append()需要的空间,比oldCapacity*2+2算出来的还大很多,会怎样?
容量4的StringBuilder真实一次性追加1000字符后容量: 1000(真实直接扩到>=1000,不是4*2+2=10)
真实结果证明扩容公式真实是 max(所需最小容量, oldCapacity*2+2) 取两者较大值——当一次性追加的内容远超"翻倍"能提供的空间时(这里公式算出来只有10,但真实需要1000),StringBuilder 会真实直接扩到刚好能装下这次内容的大小,不会先扩到10再继续扩容好几轮。
trimToSize前真实容量: 1000 trimToSize后真实容量: 5(真实收缩到刚好装下内容的长度)
真实结果验证了 trimToSize() 这个不太常用但很实用的方法——把内部数组真实收缩到恰好等于当前内容长度,释放掉扩容过程中积累的冗余空间,适合在"确定不会再有大量追加,但要长期持有这个StringBuilder"的场景下调用,减少内存占用。
每一行都是真实运行这段代码得到的结果。