StringBuffer和StringBuilder的源碼解析及擴容機制

  • StringBuffer:可變的字符序列;線程安全的,效率低;底層使用char[]存儲
  • StringBuilder:可變的字符序列;jdk5.0新增的,線程不安全的,效率高;底層使用char[]存儲

StringBuffer和StringBuilder在底層實現和內存結構上是一樣的,它們的方法的作用也是一樣的,只不過StringBuffer裏的方法都被聲明爲了同步方法(用synchronized關鍵字修飾),所以才產生了線程安全和線程不安全的問題。

由於它們的底層實現內存結構都是一樣的,所以現在以StringBuffer爲例進行說明。

1.StringBuffer()的初始容量可以容納16個字符,當該對象的實體存放的字符的長度大於16時,實體容量就自動增加。StringBuffer對象可以通過length()方法獲取實體中存放的字符序列長度,通過capacity()方法來獲取當前實體的實際容量。

2.StringBuffer(int size)可以指定分配給該對象的實體的初始容量參數爲參數size指定的字符個數。當該對象的實體存放的字符序列的長度大於size個字符時,實體的容量就自動的增加。以便存放所增加的字符。

3.StringBuffer(String s)可以指定給對象的實體的初始容量爲參數字符串s的長度額外再加16個字符。當該對象的實體存放的字符序列長度大於size個字符時,實體的容量自動的增加,以便存放所增加的字符。

StringBuffer構造器的源碼

我們先來看看StringBuffer的空參構造器:
在這裏插入圖片描述
構造一個string buffer,該string buffer爲空,在沒有傳參的情況下默認初始容量爲16個字符。
即: char[16]


再看下面的例子:

StringBuffer stringBuffer1 = new StringBuffer("ABC");

此時調用的是StringBuffer的有參構造,源碼爲:
在這裏插入圖片描述
父類的構造器:
在這裏插入圖片描述
從:super(str.length() + 16);
可以看到此時的長度在 “ABC”.length() 的基礎上又加了16,即19

所以super(str.length() + 16)
這行代碼的作用就是在方法區的字符串常量池把原來的 char[16] 改爲了char[19]

需要注意的是:如果我們此時打印stringBuffer1的長度,會發現它不是19,而是3

        StringBuffer stringBuffer1 = new StringBuffer("ABC");
        System.out.println(stringBuffer1.length()); //返回的結果爲:3

爲什麼是3呢?

我們從上述例子可以看出,有參數的情況下,初始容量是16+字符串的長度,並且是用append()方法追加的字符。

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

答案就在 append() 方法裏,於是我們找到append的源碼:
在這裏插入圖片描述
答案就在這裏,str爲我們要添加的字符串,首先程序進來會先判斷 str 是否爲空,如果爲空執行 appendNull() 方法並返回。
然後,int len = str.length() 這個len是傳入的參數 (String str) 自身的長度
str.length()調用的是String類裏的length()方法,length()方法用於返回字符串的長度,長度等於字符串中 16 位 Unicode 代碼單元的數量,即char型數組中元素的個數。“ABC”爲3個元素,所以返回 3 。
然後count+len後,返回count,由於一開始count是0,len是3,所以這裏的count就返回3了


StringBuffer擴容

再假設我new了一個空參的StringBuffer,
StringBuffer stringbuffer2 = new StringBuffer();
然後append()超過了16的長度,底層的char型數組存不下了,會怎樣呢?
這時候會擴容底層的數組

我們看到有個 ensureCapacityInternal() 的方法,該方法是的中文翻譯是 “確保內部容量”。很明顯這個方法就是爲了保證內部容量足夠而進行底層數組擴容的方法:

 ensureCapacityInternal(count + len);

我們點進去源碼:
在這裏插入圖片描述
minimumCapacity就是剛纔傳入的count+len,if語句判斷,如果符合,則調用裏面的邏輯
然後我們進入newCapacity(minimumCapacity)方法:

在這裏插入圖片描述
然後發現它是這麼擴容的 int newCapacity = (value.length << 1) + 2;
<< 是位運算符,相當於乘以2,即原來的位數擴容爲原來的兩倍,然後再加2;
這個時候如果還是放不下,那就直接擴容到它需要的長度 newCapacity = minCapacity;

小結:
擴容問題:如果要添加的數據底層數組存不下了,那就需要擴容底層的數組。默認情況下,擴容爲原來容量的2倍+2,同時將原有數組中的元素複製到新的數組中。

擴容策略(2*n + 2)

StringBuffer在內部維護一個字符數組,當你使用缺省的構造函數來創建StringBuffer對象的時候,因爲沒有設置初始化字符長度,StringBuffer的容量被初始化爲16個字符,也就是說缺省容量就是16個字符。

當StringBuffer達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,也就是(2舊值+2)。

如果你使用缺省值,初始化之後接着往裏面追加字符,在你追加到第16個字符的時候它會將容量增加到34(216+2),當追加到34個字符的時候就會將容量增加到70(2*34+2)。

StringBuffer擴容是2倍+2?爲什麼要+2?

請注意傳入參數int,意味着這裏傳入參數可以是0,那麼在參數是0的情況下,0<<1運算結果也是0,那麼在初始化數組的時候必然會報錯,所以作爲設計的安全性考慮,這裏防止出現報錯,選擇了+2,如有疑問請留言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章