Java StringBuffer与StringBuilder扩容

5.2 Java StringBuffer与StringBuilder扩容机制

StringBuilder与StringBuffer的append方法本质都是调用其父类的append方法,因此他们的扩容机制是一样的,都与父类AbstractStringBuilder进行实现.

1.append方法实现

以StringBuffer为例:

@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

super.append(str);(AbstractStringBuilder类)

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

//String类:getChars
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

appendNull():

private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

ensureCapacityInternal(count + len);:扩容机制实现:

//  ensureCapacityInternal(count + len); 
private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
//newCapacity(minimumCapacity)
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

// hugeCapacity(minCapacity)
 private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
2.整个流程总结

查看代码可以发现str=null时其实相当于append(“null”);因此只讨论str!=null的情况.整个过程如下:

1.获取追加字符串长度

2.调用容量确认方法ensureCapacityInternal,确认是否需要扩容.

3.调用String的getChars方法,实现字符串追加(本质是把字符序列拷贝到传入char数组中的指定位置)

4.更新当前字符序列长度,保存到count中并返回

扩容机制的实现在ensureCapacityInternal(count+len)方法中:

1.判段minimumCapacity - value.length >0是否成立

判断是否需要扩容;若追加字符序列长度与当前字符序列长度和超过最大容量,则触发扩容机制;

否则不进行操作;

2.若成立:进行扩容

​ 分两步走,确认扩容后的字符数组大小;创建新数组并把原字符序列复制到新数组中

2.1 newCapacity(minimumCapacity)确认新数组大小
1.在value.length(扩容器数组最大容量)*2+2与count+len(追加字符序列与当前字符序列长度和)之间确认一个预选值.
确认规则:newCapacity-minCapacity<0成立,则预选值为value.length(扩容器数组最大容量)*2+2;不成立,预选值为count+len
2.判断预选值是否溢出或者小于MAX_ARRAY_SIZE(Intege.MAX_VALUE)
3.为溢出且小于MAX_ARRAY_SIZE,该预选值即为扩容后的数组大小
4.溢出或超过MAX_ARRAY_SIZE,调用hugeCcapacity方法并传入minCapacity(即count+length),内部逻辑是
若minCapacity溢出,抛出OutOfMemoryError错误
若未溢出,与MAX_ARRAY_SIZE比较大小,较大者作为扩容后的容量
3.流程中一些细节问题思考

因为中间存在溢出,那么是否存在bug?

在解答这些问题前,有几个东西需要知道:

 System.out.println(Integer.MIN_VALUE==Integer.MAX_VALUE+1);//TRUE
 System.out.println(Integer.MIN_VALUE-1==Integer.MAX_VALUE);//TRUE
System.out.println((Integer.MAX_VALUE<<1)+2);//0
 System.out.println((Integer.MAX_VALUE/2<<1)==Integer.MAX_VALUE-1);//true
3.1 是否存在minimumCapacity 溢出时,minimumCapacity - value.length >0变为负数加负数,那么负数加负数可能会小于0,也可能会大于0.如果小于0,就会出现应该扩容但是没有扩容的bug,真的会这样吗?

不会,证明如下:

value.length是不会溢出的,即0<=value.length<=Integer.MAX_VALUE;

minimumCapacity - value.length=Integer.MAX_VALUE+(count+len-Integer.MAX_VALUE)-value.length

​ =(Integer.MAX_VALUE-value.length)+(count+len-Integer.MAX_VALUE)

经过转换,变成了正数加正数的形式,但是溢出了依然会小于0,那么会溢出吗?

在变换一下:(Integer.MAX_VALUE-value.length)+(count+len-Integer.MAX_VALUE)=len-(value.length-count)的形式.

很显然,正数减正数不会溢出.

那么正数加正数大于0恒成立.即minimumCapacity - value.length=Integer.MAX_VALUE+(count+len-Integer.MAX_VALUE)-value.length

​ =(Integer.MAX_VALUE-value.length)+(count+len-Integer.MAX_VALUE)>0恒成立.

最终得出结论,即使count+len溢出,minimumCapacity - value.length >0也会成立,依然会触发扩容机制.

3.2扩容机制中的预选值确认标准(newCapacity-minCapacity<0),当ewCapacity或minCapacity溢出时,是否存在bug

不存在bug.

无外乎以下四种情况:

1.newCapacity未溢出,minCapacity未溢出.

此时newCapacity-<0等价于比较二者大小,谁大选择谁的值作为预选值,没什么问题

2.newCapacity未溢出,minCapacity溢出

按逻辑来说,此时应该抛出异常,因为超出数组的最大表示范围,那么源码是否存在bug,会用newCapacity作为新数组空间大小吗?

其实类似于minimumCapacity - value.length >0恒成立,newCapacity-minCapacity<0在newCapacity未溢出,minCapacity溢出情况下也是恒成立的.证明如下:

newCapacity未溢出(其范围在[2,Integer.Max_VALUE-1])=>value.length范围为:[0,Integer.MAX_VALUE/2-1]:

(因为Integer.MAX_VALUE/2乘以2加2溢出.另外,Integer.Max_VALUE是奇数,因此Integer.MAX_VALUE/2乘以2结果为Integer.MAX_VALUE/2,所以newCapacity<=(Integer.MAX_VALUE/2-1)*2+2=Integer.MAX_VALUE-1-2+2=Integer.MAX_VALUE-1)

minimumCapacity =count+len=Integer.MAX_VALUE+c(c是一个数)

因为0<=count<=Integer.MAX_VALUE/2-1;

len+count>Integer.MAX_VALUE;

所以:

0<=c<=count<=Integer.MAX_VALUE/2-1<=len<=Integer.MAX_VALUE

newCapacity-minCapacity=2+value.length-c+value.length-Integer.MAX_VALUE

value.length-c>0 value.length-Integer.MAX_VALUE<0

当value.length=Integer.MAX_VALUE/2-1,c=1(c=0,Integer.MAX_VALUE+c不会越界)时,value.length-c最大,为Integer.MAX_VALUE/2-2;

Integer.MAX_VALUE/2-1时,value.length-Integer.MAX_VALUE最大为-Integer.MAX_VALUE/2-1

综上,value.length=Integer.MAX_VALUE/2-1,c=1时,

newCapacity-minCapacity=2+value.length-c+value.length-Integer.MAX_VALUE<=2+Integer.MAX_VALUE/2-2+(-Integer.MAX_VALUE/2-1)=2-2=-1<0

newCapacity-minCapacity<0在(value.length<<1)+2唯一出,count+len溢出的情况下恒成立.

此时去minCapacity作为预选值,溢出会调用huageCapacity(minCapacity),抛出OutOfMemoryError错误.

3.newCapacity溢出,minCapacity未溢出

newCapacity - minCapacity<0成不成立都不重要.

若成立:

比如newCapacity=0, minCapacityminCapacity=Integer.MAX_VALUE/2+1,
则newCapacity-minCapacity=0-(Integer.MAX_VALUE/2+1)<0;
条件成立,会用minCapacity的值和MAX_ARRAY_SIZE值比较得出最终结果

若不成立,

如newCapacity=Integer.MIN_VALUE,minCapacityminCapacity=Integer.MAX_VALUE/2+1;则
newCapacity-minCapacity=Integr.MAX_VLAUE
+1-(Integer.MAX_VALUE/2+1)>0
但是newCapacity本身溢出,会调用hugeCapacity

依然是用minCapacity的值和MAX_ARRAY_SIZE值比较得出最终结果
二者最终走的代码是一致的

4.newCapacity溢出,minCapacity溢出

newCapacity - minCapacity<0成不成立无所谓.

最终的预选值是溢出的,最终都会把 minCapacity传给hugeCapacity方法中.抛出OutOfMemoryError无误

综上,四种情况下都不会出现逻辑上的问题,该程序不存在bug

4.总结

平常没有考虑溢出的问题,导致存在一些误区。比如a-b>0并不等价于a>b;

如果判断是否扩容的条件minimumCapacity - value.length >0改为minimumCapacity > value.length

很显然当minimumCapacity =count+len溢出时,不满足minimumCapacity > value.length继而出现应该扩容但未扩容的情况.

同时,也能总结出一下规律:

如果:
int c=a+b;(a,b未正数) 且a+b>Integr.MAX_VALUE
c-k>0;一定成立(k一个正数)   
k-c<0;也一定成立.    
  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值