今天分析的兩個類是:StringBuffer 和 StringBuilder。開篇前,先看看它們的繼承層次:html
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {...} public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {...}
都繼承了 AbstractStringBuilder ,實現了 Serializable 和 CharSequence 接口。final類型,不能再派生子類。java
(1) char[] value;// 底層都是用字符數組char[]實現,存儲字符串,默認的大小爲16。在父類 AbstractStringBuilder 中定義的。String的value數組使用final修飾,不能變更,StringBuffer和StringBuilder的value數組沒有final修飾,是可變的。數組
關於數組的大小,默認的初始化容量是16。這個數有木有想起了Map的實現子類的初始容量。假如初始化的時候,傳入字符串,則最終的容量將是 (傳入字符串的長度 + 16) 。緩存
(2) private transient char[] toStringCache;// StringBuffer特有,緩存toString最後一次返回的值。安全
若是屢次連續調用toString方法的時候因爲這個字段的緩存就能夠少了Arrays.copyOfRange的操做(每次調用其餘的修改StringBuffer對象的方法時,這些方法的第一步都會先將toStringCache設置爲null,詳細參見源碼)app
StringBuilder.toString() 函數
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
StringBuffer.toString() 性能
public synchronized String toString() { if (toStringCache == null) {// toStringCache爲空,第一次操做 toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true);// 使用緩存的toStringCache,實際只傳遞了引用,沒有複製操做 }
String 提供了一個保護類型的構造方法。目前不支持使用false,只使用true。那麼能夠判定,加入這個share的只是爲了區分於String(char[] value)方法,不加這個參數就沒辦法定義這個函數,只有參數不一樣才能進行重載。那麼,第二個區別就是具體的方法實現不一樣。這裏直接將value的引用賦值給String的value。那麼也就是說,這個方法構造出來的String和參數傳過來的char[] value共享同一個數組。做用的話,確定是性能好一點。假如把該方法改成public,而不是protected的話,對外開放訪問,就能夠經過修改數組的引用來破壞String的不可變性。ui
String(char[] value, boolean share) { // assert share : "unshared not supported"; this.value = value;// 沒有真正複製,只是賦值引用 }
對比一下,下面是實際複製了數組元素的: this
public String(char value[]) { this.value = Arrays.copyOf(value, value.length);// 用到Arrays的copyOf方法將value中的內容逐一複製到String當中 }
二者方法最大的區別是:StringBuffer是線程安全的,StringBuilder是非線程安全的。實現是StringBuffer在和StringBuilder相同的方法上加了 synchronized 修飾。
StringBuffer.append(String)
public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
StringBuilder.append(String)
public StringBuilder append(String str) { super.append(str); return this; }
經過分析源碼,發現每次須要擴容的都是按照 "之前容量*2+2" 進行擴容,若是擴容以後仍不知足所需容量,則直接擴容到所需容量。
對外經過 ensureCapacity(size) 來主動擴容。
public void ensureCapacity(int minimumCapacity) { if (minimumCapacity > 0) ensureCapacityInternal(minimumCapacity); }
AbstractStringBuilder.ensureCapacityInternal(int) private修飾,供類內部調用。newCapacity是在JDK1.8中拆分出來的方法,以前擴容都是在一個方法裏面操做完成的 - expandCapacity
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value,newCapacity(minimumCapacity)); } } private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2;// 之前的容量*2 + 2 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; }
特地去對比了一下JDK1.7的源碼:
AbstractStringBuilder.ensureCapacity(int)
AbstractStringBuilder.ensureCapacityInternal(int)
AbstractStringBuilder.expandCapacity(int)
JDK1.8的源碼:
AbstractStringBuilder.ensureCapacity(int)
AbstractStringBuilder.ensureCapacityInternal(int)
AbstractStringBuilder.newCapacity(int)
AbstractStringBuilder.hugeCapacity(int)
經過對比發現,JDK8把以前在一個方法裏面作的操做拆分紅了兩個方法,看源碼的時候,更容易理解。
1.相同點
(1)繼承層次相同,都繼承了 AbstractStringBuilder ,實現了 Serializable 和 CharSequence 接口;
(2)底層都是用字符數組實現,字符串都是可變的,區別於String;
(3)初始容量都是16,擴容機制都是"之前容量*2+2"
2.不一樣點
(1)StringBuilder不是線程安全的,StringBuffer是線程安全的(方法上多了synchronized修飾);
(2)StringBuffer比StringBuilder多了一個toStringCache字段,用來在toString方法中進行緩存;
(3)StringBuilder沒有加同步,在不會出現線程安全問題的狀況下,性能上StringBuilder應該要高於StringBuffer
今晚看源碼的時候,發現上面的數組容量有個最大值,很好奇,點進去看一下:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;// Some VMs reserve some header words in an array.
已有的註釋意思是:有些虛擬機在數組中保留了一些頭信息。避免內存溢出。
MAX_VALUE是0x7fffffff,即 2^31 = 2,147,483,648 。那爲啥最大數組的大小是 2^31 減掉 8 呢?8這個數字恰好是一個字節裏面比特的數量,讓人聯想翩翩。
而後去gg了一下:在StackOverflow上,有個已經解答的問題,Why the maximum array size of ArrayList is Integer.MAX_VALUE - 8?
回答參考了developerworks的論文:Java Memory management
緣由是:數組須要 8 byte 來存儲本身的大小數量,因此最大數組定義爲 Integer.MAX_VALUE - 8。