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;也一定成立.