StringBuffer與StringBuilder

說明:本文是閱讀《Java程序性能優化》(作者:葛一明)一書中關於StringBuffer與StringBuilder一節的筆記。


一、String常量與變量的累加操作

1、String常量的累加操作

由於String對象具有不可變性,所以String對象一旦生成就無法被改變,所以如下所示的字符串累加操作總共會產生7個String對象,因此從理論上講這段代碼的效率並不高。

String str = "String" + "and" + "StringBuilder" + "append";

此時就可以用到StringBuffer或者StringBuilder類了,使用StringBuilder類完成同樣的功能如下:

StringBuilder sb = new StringBuilder();
sb.append("String").append("and").append("StringBuilder").append("append");
使用StringBuilder後僅僅會生成4個String對象與1個StringBuilder對象,效率會高於直接累加的方式。但是,如果將以上兩段代碼分別執行100000次,得到的結果卻是令人驚訝的,在我的機器上測試得到的用時分別是1ms(直接累加)與大概40ms(使用StringBuilder)左右。爲什麼呢?使用反編譯工具對第一段代碼進行反編譯,得到的結果如下:

String str = "StringandStringBuilderappend";
從反編譯結果來看,對於常量字符串的累加,Java在編譯時就做了優化,對於在編譯時就能確定取值的字符串的操作,在編譯時就進行了計算,因此在運行時該段代碼並沒有像想象中那樣生成7個String對象。而對於上面的第二段代碼,反編譯得到的結果與源代碼一樣,所以在運行時StringBuilder對象和append()方法都被如實的調用,所以相比之下,第一段代碼的執行效率卻更高。
2、String變量的累加操作

如下代碼所示,現在將每個字符串都定義在一個變量中,然後再對這邊變量進行累加操作,這樣在編譯時就無法確定str變量的取值:

String s1 = "String";
String s2 = "and";
String s3 = "StringBuilder";
String s4 = "append";
String str = s1 + s2 + s3 + s4;
書上說的是通過反編譯後,生成的代碼會是如下所示的樣子:

String s1 = "String";
String s2 = "and";
String s3 = "StringBuilder";
String s4 = "append";
String str = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();
但是我通過JD-GUI反編譯工具查看得到的反編譯代碼並不是這樣的(JDK1.6u45),卻是和原來的代碼保持一模一樣,有可能是使用的編譯器不一樣的原因吧。接着我實驗了常量字符串與變量字符串混合使用的情況,源代碼如下:

String s1 = "String";
String s2 = "and";
		
String str1 = "Hello" + "String" + s1 + s2 + "Hello" + "StringBuilder";
String str2 = s1 + s2 + "Hello" + "StringBuilder";
String str3 = s1 + "Hello" + "StringBuilder" + s2;
接着使用反編譯工具反編譯得到的代碼如下:可見,在由常量字符串和變量字符串混合累加的情況下,開始部分的常量字符串會得到優化,在編譯時就計算出來了,其餘情況下都不會得到優化。

String s1 = "String";
String s2 = "and";
    
String str1 = "HelloString" + s1 + s2 + "Hello" + "StringBuilder";
String str2 = s1 + s2 + "Hello" + "StringBuilder";
String str3 = s1 + "Hello" + "StringBuilder" + s2;

總之,對於字符串的累加操作,在某些情況下編譯器會做一定的優化,但是別依賴於編譯器的這些優化,因爲編譯器也不是絕對的那麼智能,還是應該在編寫程序時就注重代碼的優化。

二、StringBuffer與StringBuilder

1、StringBuffer與StirngBuilder的功能都幾乎一樣,最大的不同之處在於StringBuffer對它的幾乎所有的方法都做了同步,而StringBuilder並沒有做任何同步。由於方法不同需要一定的系統資源,所以StringBuilder的效率要好於StringBuffer,但是在多線程中,StringBuilder無法保證線程安全,而StringBuffer卻可以。

2、容量參數

StringBuffer和StirngBuilder在初始化時都可以指定一個容量參數(默認是16個字節),該參數指定了它們的初始大小,如下圖是它們的父類的一個構造函數:

當在追加字符串時,如果其容量已經超過了char數組長度,則需要進行擴容,擴容的函數定義如下圖:

從中可以發現,擴容的策略是將原有的容量大小翻倍來申請內存空間,然後建立新的char數組,最後再將原數組中的內容複製到新的數組中,所以對於大對象的擴容會涉及大量的內存複製操作,如果能夠預先估計其容量大小,則能有效的節省這些操作,從而提高效率。如下代碼所示,沒指定容量參數時在我的機器上執行大概需要600ms左右的時間,而指定了初始容量後大概需要500ms左右的時間。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5000000; i++) {
	sb.append(i);
}

StringBuilder sb2 = new StringBuilder(5000000);
for (int i = 0; i < 5000000; i++) {
	sb2.append(i);
}

發佈了60 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章