首先,從String類的定義入手,可以看到String類是由final修飾,即不可變的,一旦建立出來就不可修改,因此首先明確,字串的拼接、擷取等操作都會產生新的字串物件。
觀察以下幾種建立Stirng的語句
1 String s1 = "hello "; 2 String s2 = "world"; 3 String s3 = s1 + s2; 4 String s4 = "hello " + "world"; 5 String s5 = new String("hello world");
1> 對於s1,直接通過字串常量建立,會先查詢常量池中是否有 hello 這個字串,有的話不建立,沒有的話新建,因此會在常量池中建立一個物件。
2> 對於s2,同上,在常量池中建立一個物件。
3> 對於s3,字串拼接操作,檢視反彙編程式碼,字串拼接實際上是new StringBuilder(),然後執行其append()方法,最後執行toString,過程中建立了3個物件。
4> 對於s4,直接通過常量字串拼接,會被優化為建立 hello world,在常量池中建立物件。
5> 對於s5,new操作一定會在堆中建立物件,然後查詢常量池中是否有 hello world 這個字串,沒有就在常量池中建立一個。
對於s3、s4、s5,字串的內容都是hello world,但對於s3和s5,引用的都是堆中建立的字串物件,s4引用的則直接是常量池中的物件,因此它們三個的引用都是不同的,如下
intern
通過上面的驗證,知道直接通過常量定義的String物件是位於常量池中,而通過new顯式建立或者拼接隱式建立的String物件是位於堆中。
除了常用的操作字串方法,JDK還提供了 inter() 方法,該方法的官方說明如下,作用是當方法呼叫時,如果常量池中已經包含一個和這個String相等(內容相同,即equals方法)的String,就會返回常量池中的物件,否則,這個物件會被新增到常量池,然後返回她的引用。簡單來說其作用是把字串快取到常量池中。
通過幾個例子來驗證intern的作用
StringBuilder和StringBuffer都表示可變的字串序列,可以通過其提供的一序列方法實現字串的拼接、擷取,觀察原始碼會發現兩個類的核心程式碼基本一致,二者都繼承自抽象類AbstractStringBuilder。
-
二者的共同點是均提供了字串的拼接、擷取等操作,無需每次變動都建立新的物件,一次建立,最後只需通過toString生成最終的字串物件即可。
-
二者的區別在於在字串拼接等操作上StringBuffer使用synchronized關鍵字修飾方法,多執行緒情況下保證了執行緒安全,當然相比StringBuilder,也降低了效能。
通過原始碼來看動態拼接字串邏輯,StringBuilder和StringBuffer都是呼叫父類的append方法實現,因此二者實現邏輯是一致的,其中count是用來記錄value陣列已經使用的長度,即在現有的value陣列後面拼接上新的str字串。因此在StringBuilder中,程式碼中的 count+=len 不是原子操作,多執行緒操作的情況下,例如當前count為5,可能出現多個執行緒拿到的count都是5,分別執行累加操作後再賦給count,得到的count只是6,此時便出現了執行緒不安全的情況。
附:雖然上面提到,通過 + 號拼接String物件時,java也會隱式地建立StringBuilder物件進行拼接,通常情況下並不會造成效能效率損失,但在需要大量迴圈拼接字串時,使用+拼接,會在每次迴圈時都建立一個StringBuilder物件,迴圈結束後,記憶體中會多出許多無用的StringBuilder物件,因此這種情況,建議在迴圈體外建立StringBuilder物件,迴圈呼叫其append()方法拼接,如此只需建立一個StringBuilder物件。
擴容
首先注意二者的初始容量都是16,當然也可以通過引數指定初始容量
1 public StringBuilder() { 2 super(16); 3 } 4 public StringBuffer() { 5 super(16); 6 }
在字串拼接時,都會呼叫父類AbstractStringBuilder的append方法,方法原始碼如下
1 public AbstractStringBuilder append(String str) { 2 if (str == null) { 3 return appendNull(); 4 } 5 int len = str.length(); 6 ensureCapacityInternal(count + len); 7 putStringAt(count, str); 8 count += len; 9 return this; 10 }
其中的 ensureCapacityInternal 方法,就是在拼接前進行擴容
1 private void ensureCapacityInternal(int minimumCapacity) { 2 // overflow-conscious code 3 int oldCapacity = value.length >> coder; 4 if (minimumCapacity - oldCapacity > 0) { 5 value = Arrays.copyOf(value, 6 newCapacity(minimumCapacity) << coder); 7 } 8 }
其中的minimumCapacity就是擴容後的字串長度,超過目前容量的話,呼叫newCapacity方法執行擴容,可以看出每次擴容後的容量是原容量的2倍加2,如果仍不夠,就直接擴容到需要的長度。
1 private int newCapacity(int minCapacity) { 2 // overflow-conscious code 3 int oldCapacity = value.length >> coder; 4 int newCapacity = (oldCapacity << 1) + 2; 5 if (newCapacity - minCapacity < 0) { 6 newCapacity = minCapacity; 7 } 8 int SAFE_BOUND = MAX_ARRAY_SIZE >> coder; 9 return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0) 10 ? hugeCapacity(minCapacity) 11 : newCapacity; 12 }