StringBuffer 和 StringBuilder, 都是继承了 AbstractStringBuilder, 它们的底层 char 数组 value 默认的初始化容量是 16, 扩容只需要修改底层的 char 数组, 两者的扩容最终都会调用到 AbstractStringBuilder 类相同的方法:
直入正题, 扩容的步骤:
新的字符串的长度超过了底层原 char 数组 value 的大小, 才需要进行扩容
先尝试默认扩容, 将新容量变成 (value.length <<1) + 2 , 也就是两倍的原数组长度再加二
若默认扩充后的值还是小于至少容量的值, 直接扩充到当前需要的至少容量大小;
经过前两步骤确定的新数组大小, 若大于 Interger.MAX_VALUE, 则报异常, 若小于等于 0, 则新数组大小改为
Interger.MAX_VALUE -8
确定了新数组的值后, 通过
Arrays.copy(value,newCapactity)
进行复制. 最终给 value 数组完成扩容.
这样扩容的目的, 宏观上是尽可能地减少扩容次数, 提高效率.
肯定有同学会问, 默认扩容为什么是两倍的原数组长度 + 2 ?
因为源码并无说明这样设计的原因, 所以根据我找到的资料结合我的推测, 可能的原因有这些:
考虑到在创建 Sf,Sb 设置的初始长度不大时 (例如 1),+2 可以很大地提升扩容的效率, 减少扩容的次数
在旧版本的 JDK 扩容语句是
(value.length + 1) * 2
先加一再乘 2, 推测原意思是扩容的话至少增添一个空间再乘 2, 兼顾到扩容的次数和要减少扩容过大浪费的空间
newCapacity(int) 的传入参数有可能是 0, 那么在参数是 0 的情况下, 0<<1 运算结果也是 0, 如果没 + 2, 那么在创建数组的时候会创建出 MAX_ARRAY_SIZE 大小, 所以作为设计的安全性考虑, 选择了 + 2.(本人认为除了反射调用 newCapacity, 其他情况应该不会出现 newCapacity(int) 可以传入 0 为参数)
append(str) 后需补充分隔符所预留的位置, 为了减少扩容次数 (个人感觉这点不太靠谱)
下面是在 JDK1.8 中 AbstractStringBuilder 有关计算扩容的方法:
- //AbstractStringBuilder.java
- private void ensureCapacityInternal(int minimumCapacity) {
- // 若所需长度大于已有长度, 才继续进行扩容
- if (minimumCapacity - value.length> 0) {
- // 通过 Arrays.copyOf(), 将旧 value 数组内容先复制到 newCapacity 大小的数组, 再赋值给新 value
- value = Arrays.copyOf(value,
- newCapacity(minimumCapacity));
- }
- }
- private int newCapacity(int minCapacity) {
- // 默认扩容: newCapacity = 两倍的原长度 + 2
- int newCapacity = (value.length <<1) + 2;
- if (newCapacity - minCapacity < 0) {// 默认扩容后还是小于所需长度
- newCapacity = minCapacity;// 直接补充至所需长度
- }
- return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
- ? hugeCapacity(minCapacity)//newCapacity>MAX_ARRAY_SIZE 或者≤0 会调用
- : newCapacity;
- }
- MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- private int hugeCapacity(int minCapacity) {
- if (Integer.MAX_VALUE - minCapacity < 0) { // 大小超出 Integer 范围爆异常
- throw new OutOfMemoryError();
- }
- return (minCapacity> MAX_ARRAY_SIZE) // 返回 minCapacity 与 MAX_ARRAY_SIZE 最大值
- ? minCapacity : MAX_ARRAY_SIZE;
- }
来源: https://www.cnblogs.com/DMingO/p/13407303.html