深度透析String與StringBuilder
阿新 • • 發佈:2019-02-12
String的不可變性帶來的一定效率問題。
public class Concatenation {
public static void main(String[] args) {
String mango="mango";
String s="abc"+mango+"def"+47;
System.out.println(res);
}
}
這種“+”會產生一大堆需要垃圾回收的中間物件。可以通過jdk自帶的javap來反編譯以上程式碼,就知道以上程式碼如何工作的。(為關注有效部分,JVM位元組碼經過有效簡化,下同)
D:\Download>javap -c Concatenation.class
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String mango
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}
這-c標誌表示將生成JVM位元組碼,其中dup和invokevirtual語句相當於Java虛擬機器上的彙編語句,可以看出重點是:編譯器自動引入了java.lang.StringBuilder類。雖然我們原始碼總沒有加入StringBuilder類,因為它高效,編譯器自動使用了它。從上邊看出編譯器建立了一個StringBuilder物件,用以構建最終的String物件,併為每個字串呼叫一次StringBuilider的append()方法,總計4次。最後呼叫StringBuilder的toString()方法生成結果,並存為s(使用的命令是astore_2)。
要有對比才能看出效能的區別,
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for (int i = 0; i < fields.length; i++) {
result += fields[i];
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
}
D:\Download>javap -c WhitherStringBuilder.class
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39: areturn
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
首先看implicit()方法,第8行到第35行構成一個迴圈體。第8行:隊堆疊中的運算元進行“大於或等於的整數比較運算”,迴圈結束時跳到第38行。第35行:返回迴圈體的起始點(第5行)。可以看到StringBuilder在迴圈體內構造,這就意味著沒經過一次迴圈就會產生一個新的StringBuilder物件。
再來看explicit()方法,這個方法的不僅迴圈程式碼更簡短,更簡單,而且它只生成了一個StringBuilder物件。同時顯式地建立StringBuilder還允許預先為其指定大小,準確知道大小,預先分好,避免多次重新分配緩衝。
因此,字串比較簡單,可使用String類,要是比較複雜或在迴圈體內使用建議使用StringBuilder類。