StringBuilder擴容原理及源碼分析
使用無參構造方法創建對象
首先我們通過StringBuilder的無參構造方法創建一個StringBuilder對象sb,
可以看到源碼中,當我們使用無參構造創建對象時,默認為我們提供了一個容量(capacity)大小為16的給其父類。
在其父類中,收到子類StringBuilder傳來的capacity值為16,,此處我們可以看到這里有一個參數COMPACT_STRINGS,為了優化字符串在JVM中的內存占用,Java 9引入了Compact Strings來取代Java 6的Compressed Strings,使用byte[]來替代char[],並且引入了一個字段coder來標識是LATIN1還是UTF-16。
進入到COMPACT_STRINGS,咱們可以看到一個靜態代碼塊,為COMPACT_STRINGS賦值為true。那么便要進入if語句中。
在if語句中,我們可以看到我們創建了名叫value的一個byte數組,coder為LATIN1,之前我有提到引入了一個字段coder來標識是LATIN1還是UTF-16,那么此處coder就標識為LATIN1。
這就是無參構造方法創建對象時,底層的源碼,可以看到現在sb對象的容量為16,長度為0,那是因為咱們還沒有存儲對象。
使用append方法添加字符串
咱們先使用append方法去添加一個大小為16個字符的對象,看看底層會怎么走。
可以看到圖上咱們使用append方法去添加一個大小為16個字符的對象時,他將這個對象丟給了他的父類AbstractStringBuilder,
進入父類,咱們可以看到添加的不為空,那么走到len這邊,在length()方法中咱們看見了老熟人coder,之前咱們有說到coder就標識為LATIN1,這里就用上了。
當coder標識為LATIN1時,表示字符串只包含 LATIN-1編碼的字符,coder 變量的值將為0,咱們現在看到的時一個三元表達式,如果是true就返回第一個值,false返回第二個值,而COMPACT_STRINGS之前是為true的,所以返回了coder=0的值。
從上面我們可以看到他的長度並沒有發生改變還是16。
接下來又看到了咱們的老朋友coder,他還是0,所以oldCapacity會等於str的長度16,minimumCapacity的值為count加length,count的初始值為0,所以minimumCapacity也是16。
這里有一個count是擴容機制中關鍵的元素,在每次添加字符串長度后,他都會記錄長度,此處count=16。
查看結果,和分析的一致,
那么在超出容量會怎么樣了?
超出容量是擴容機制的底層原理
再添加一個字符串,來看看再超出容量時,StringBuilder的擴容機制怎么實現的。
首先,依然是調用StringBuilder的append方法將這個字符串給了父類,
父類先判斷傳過來的字符串是不是空的,不是空的,就到len這一步,
可以看到coder還是0,len為1;
而ensureCapacityInternal中的參數count在之前添加過元素后變為16了,所以這邊的minimumCapacity其實是等於17的;
然而value.length依然還是16,minimumCapacity-oldCapacity
現在已經是大於0了,
擴容機制的核心代碼來了:
value = Arrays.copyOf(value,newCapacity(minimumCapacity) << coder);
這句代碼的意思是復制value數組的內容,長度為newCapacity(minimumCapacity) << coder。
之前minimumCapacity是等於17了的。oldCapacity依然為16,但是咱們注意到int newCapacity = (oldCapacity << 1) + 2;
(<<的意思是二進制向左移動一位,移動一位相當於乘以2,移動幾位就是乘以2的幾次冪。>>的意思就是向右移動,移動幾位就相當於除以2的幾次冪。)
這句話可以使用一個式子代替為:新的容量=舊的容量*2+2,所以我們不難得出新的容量為34。
當添加字符串超過 當前容量*(2^1)時:
此處我添加35個字符,得到的結果好像和之前沒啥關系,接着往下看。
可以看見minCapacity變為35,oldCapacity為16,newCapacity=16*(2^1)+2=34。
newCapacity-minCapacity<0進入if;
把minCapacity的值復制給了newCapacity。這就是容量等於35,長度也是35的原因。
使用有參構造方法創建對象
使用有參構造方法創建StringBuilder對象,
這里我也創建一個大小為14的字符串對象,看看是否和無參是一樣的。
可以看見和無參明顯不一樣,無參中的super方法中值為16,而有參方法確實字符串的長度加16,super(str.length() + 16);
進入length()方法算字符串的長度為14,14+16=30,所以他的容量為30。
接着往下走,COMPACT_STRINGS為true(前面有說到),進去if語句中,創建了一個大小為capacity(30)的byte數組,coder還是標識的LATIN1。
然后使用append方法把字符串添加進之前創建的byte數組(與之前使用append同理)。
綜上我們可以知道:
1、無參調用時,我們的字符串默認初始容量為16.
2、 a、當添加字符串不超過容量時,容量不發生變化,長度為字符串長度。
b、當添加字符串超過容量時,容量發生變化,計算公式為:
添加后容量=當前容量*(2^1)
適用於添加字符串長度+加上本身長度<當前容量*(2^1)的情況;
c、當添加的字符串超過當前容量*(2^1)時:
添加后容量=字符串長度+當前容量
3、StringBuilder存儲對象,說白了就是底層了byte數組在進行存儲。
我們在實際應用中應當避免StringBuilder頻繁的擴容,節約資源,如果將來有需要使用StringBuilder方法添加字符串,且不知道添加多少時,可以使用StringBuilder(int capacity)方法構建一個沒有字符的初始容量為capacity的字符串構造器,避免資源浪費。