StringBuilder擴容原理及源碼分析


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的擴容機制怎么實現的。

 

 

首先,依然是調用StringBuilderappend方法將這個字符串給了父類,

 

父類先判斷傳過來的字符串是不是空的,不是空的,就到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,oldCapacity16,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語句中,創建了一個大小為capacity30byte數組,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的字符串構造器,避免資源浪費。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM