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;
}