從記憶體方面解釋Java中String與StringBuilder的效能差異

從記憶體方面解釋Java中String與StringBuilder的效能差異

以前經常在網上看到關於Java字串拼接等方面的討論。看到有些Java開發人員在給新手程式設計師的建議中類似如下寫道:

不要使用 號拼接字串,要使用StringBuffer或StringBuilder的append()方法來拼接字串。
不過,用 號拼接字串就真的那麼令人討厭,難道使用 號拼接字串就沒有一點可取之處嗎?

通過查閱Java API文件中關於String類的部分內容,我們可以看到如下片段:
“Java 語言提供對字串串聯符號(” “)以及將其他物件轉換為字串的特殊支援。字串串聯是通過 StringBuilder(或 StringBuffer)類及其 append 方法實現的。字串轉換是通過 toString 方法實現的,該方法由 Object 類定義,並可被 Java中的所有類繼承。”

這段話很明確地告訴我們,在Java中使用 號拼接字串,實際上使用的就是StringBuffer或StringBuilder及其append方法來實現的。

除了Java API文件,我們還可以使用工具檢視class類檔案的位元組碼命令來得到上述答案。 例如程式碼:


public static void main(String[] args) {
String a = "Hello";
String b = " world";
String str = a   b   " !";
System.out.println(str);
}

通過工具檢視到其對應的位元組碼命令如下:

https://codertw.com/wp-content/uploads/2018/06/20180630061001-5b371eb95da13.jpg (939×352)

從位元組碼命令中,我們可以清楚地看到,我們編寫的如下程式碼


String str = a   b   " !";

被編譯器轉換成了類似如下語句:


String str = new StringBuilder(String.valueOf(a)).append(b).append(" !").toString();

不僅如此,Java的編譯器也是一個比較聰明的編譯器,當 號拼接的全部是字串字面量時,Java的編譯器將會在編譯時智慧地將其轉換為一個完整的字串。例如:


public static void main(String[] args) {
String str = "Hello"   " world"   ", Java!";
System.out.println(str);
}

Java編譯器直接將這種全是字面量的字串拼接,在編譯時就轉換為了一個完整的字串。

https://codertw.com/wp-content/uploads/2018/06/20180630061001-5b371eb9a6d00.jpg (707×145)

就算 號拼接的字串中存在變數,Java編譯器也會將最前面的字串字面量合併為一個字串。


public static void main(String[] args) {
String java = ", Java!";
String str = "Hello"   " world"   java;
System.out.println(str);
}

https://codertw.com/wp-content/uploads/2018/06/20180630061001-5b371eb9cf566.jpg (935×265)

從上述可知,對於類似String str = str1 str2 str3 str4,這種將多個字串一次性拼接的操作,使用 號來進行拼接是完全沒有問題的。

在Java中,String物件是不可變的(Immutable)。在程式碼中,可以建立多個某一個String物件的別名。但是這些別名都是的引用是相同的。
比如s1和s2都是”droidyue.com”物件的別名,別名儲存著到真實物件的引用。所以s1 = s2


String s1 = "droidyue.com";
String s2 = s1;
System.out.println("s1 and s2 has the same reference ="   (s1 == s2));

並且在Java中,唯一被過載的運算子就是字串的拼接相關的。 , =。除此之外,Java設計者不允許過載其他的運算子。

在Java中,唯一被過載的運算子就是字串的拼接相關的。 , =。除此之外,Java設計者不允許過載其他的運算子。

眾所周知,在Java 1.4版本之前,字串拼接可以使用StringBuffer,從Java 1.5開始,我們可以使用StringBuilder來拼接字串。StringBuffer和StringBuilder的主要區別在於:StringBuffer是執行緒安全的,適用於多執行緒操作字串;StringBuilder是執行緒不安全的,適合單執行緒下操作字串。不過,我們的大多數字串拼接操作都是在單執行緒下進行的,因此使用StringBuilder有利於提高效能。

在Java 1.4之前,編譯器使用StringBuffer來處理 號拼接的字串;從Java 1.5開始,編譯器大多數情況下都使用StringBuilder來處理 號拼接的字串。

當我們在JDK 1.4的環境下編寫程式碼時,對於上述這種一次性拼接多個字串的情況,建議最好使用 號來處理。這樣,當JDK 升級到1.5及以上版本時,編譯器將會自動將其轉換為StringBuilder來拼接字串,從而提高字串拼接效率。

當然,推薦使用 號拼接字串也僅限於在一條語句中拼接多個字串時使用。如果分散在多條語句中拼接一個字串,仍然建議使用StringBuffer或StringBuilder。 例如:


public static void main(String[] args) {
String java = ", Java!";
String str = "";
str  = "Hello";
str  = " world";
str  = java;
System.out.println(str);
}

編譯器編譯後的位元組命令如下:

https://codertw.com/wp-content/uploads/2018/06/20180630061002-5b371eba2bb83.jpg (945×603)

從上面的圖片中我們可以知道,每一條 號拼接語句,都建立了一個新的StringBuilder物件。這種情況在迴圈條件下表現得尤其明顯,造成了相對較大的效能損耗。因此,在多條語句中拼接字串,強烈建議使用StringBuffer或StringBuilder來處理。

關於使用StringBuilder帶來的優化

此外,在使用StringBuffer或StringBuilder的時候,我們還可以使用如下方式,進一步提高效能(下面程式碼以StringBuilder為例,StringBuffer與此類似)。

1.預測最終獲得的字串的最大長度。

StringBuilder內部char陣列的預設長度為16,當我們append追加字串後超過此長度時,StringBuilder會擴大內部的陣列容量以滿足需要。在這個過程中,StringBuilder會建立一個新的較大容量的char陣列,並將原陣列中的資料複製到新陣列中。如果我們能夠大致預測到最終拼接得到的字串的最大長度,就可以在建立StringBuilder物件時指定合適大小的初始容量。例如,我們需要拼接獲得含有100個字母a的字串。即可編寫如下程式碼:


StringBuilder sb = new StringBuilder(100);
for (int i = 0; i < 100; i  ) {
sb.append('a');
}
System.out.println(sb);

請根據實際情況進行平衡,以建立適合初始容量的StringBuilder。

2.對於單個字元,儘可能地使用char型別,而不是String型別。

有些時候,我們需要在字串後追加單個字元(例如:a),此時應儘可能地使用


sb.append('a');

而不是使用:


sb.append("a");

您可能感興趣的文章:

詳細分析Java中String、StringBuffer、StringBuilder類的效能深入剖析java中String、StringBuffer、StringBuilder的區別淺析java中stringBuilder的用法Java StringBuilder和StringBuffer原始碼分析Java中String、StringBuffer、StringBuilder的區別介紹Java中StringBuffer和StringBuilder區別Java中的StringBuilder效能測試java中String與StringBuilder的區別全面解釋java中StringBuilder、StringBuffer、String類之間的關係Java之String、StringBuffer、StringBuilder的區別分析Java中StringBuilder字串型別的操作方法及API整理