for循環中為什么最好使用StringBuilder?
此次操作都是在以下環境中
環境 | 版本 |
---|---|
archlinux | 4.16.13-1-ARCH |
jdk | java version "1.8.0_172" |
先看不使用StringBuilder的情況
public class Test {
public static void testForStr(){
String str = "";
for (int i=0; i<10; i++){
str += i;
}
}
public static void main(String[] args){
Test.testForStr();
}
}
為了更好的分析編譯器到底干了什么,我們需要使用javap命令進行字節碼分析,終端執行以下命令:
javac Test.java
javap -c Test.class
屏幕輸出的結果為:
Compiled from "Test.java"
public class top.freesh.laughably.Test {
public top.freesh.laughably.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void testForStr();
Code:
//從常量池引用#2並推向棧頂
0: ldc #2 // String abc
//將棧頂的引用存入第一個局部變量
2: astore_0
//將int型常量0推入棧頂
3: iconst_0
//將棧頂0存入第二個int型變量中
4: istore_1
//第二個int局部變量進棧
5: iload_1
//將byte型常量10推入棧頂
6: bipush 10
//如果棧頂兩個值大於等於0(此時0-10)則跳轉36(code)
8: if_icmpge 36
//創建StringBuilder對象,其引用進棧
11: new #3 // class java/lang/StringBuilder
//復制棧頂引用並進棧(此時棧頂有兩個對象的引用)
14: dup
//調用StringBuilder的構造方法(消耗掉一個對象引用)
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
//第一個引用變量入棧(第一次為:abc)
18: aload_0
//調用append方法,消耗一個對象引用和一個abc,並返回一個對象引用存入棧頂(注意到append()方法返回的是StringBuilder引用)
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//第二個int型變量入棧(0入棧,此處對應循環體中的i)
22: iload_1
//調用append方法,消耗一個對象引用和o,並返回一個對象引用存入棧頂(同19)
23: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
//調用toString方法,並將產生的String存入棧頂
26: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
//將棧頂String存入本地第一個引用變量中
29: astore_0
//將本地第二個int型變量增加1
30: iinc 1, 1
//無條件跳轉到5(code)
33: goto 5
//返回
36: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #8 // Method testForStr:()V
3: return
}
可見for循環中,對字符串進行加法操作,都會產生一個StringBuilder對象,這雖然減少了jvm常量池的壓力,但也無疑增加了jvm中新生代的壓力(增加了垃圾回收的幾率)
再來看看使用StringBuilder的情況吧
public class Test {
public static void testForStr(){
//和上邊的代碼邏輯一樣,只是這里使用StringBuilder連接字符串
StringBuilder stringBuilder = new StringBuilder("abc");
for (int i=0; i<127; i++){
stringBuilder.append(i);
}
}
public static void main(String[] args){
Test.testForStr();
}
}
使用同樣的命令分析jvm指令
javac Test.java
javap -c Test.class
Compiled from "Test.java"
public class top.freesh.laughably.Test {
public top.freesh.laughably.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void testForStr();
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
//存儲的是StringBuilder引用
9: astore_0
10: iconst_0
11: istore_1
12: iload_1
13: bipush 10
15: if_icmpge 30
18: aload_0
19: iload_1
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
//此處將append產生的對象引用(此時位於棧頂)出棧,因為aload_0每次都會將引用入棧
23: pop
24: iinc 1, 1
27: goto 12
30: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #6 // Method testForStr:()V
3: return
}
完全沒有問題,每次循環也不會產生StringBuilder對象了
-
建議大家以后在循環中使用StringBuilder來操作字符串拼接工作
-
普通代碼中使用加號連接字符串會被jvm優化成使用StringBuilder拼接(感興趣的朋友可以試一下)
本博客為博主原創,轉載請注明出處