為了研究javac對于String相關代碼的位元組碼優化,我做了如下測試。
測試環境:
$ javac -version
javac 1.6.0_23
$ java -version
java version "1.6.0_23"
OpenJDK Runtime Environment (IcedTea6 1.11pre) (6b23~pre11-0ubuntu1.11.10.2)
OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)
1.編寫代碼:
String name = "ab"+"cd";
String name1 = "ab" + new String("cd");
String name2 = "ab"+1+"cd";
String name3 = "ab"+"c" + new String("d");
StringBuffer name4 = new StringBuffer("e");
name4.append("f").append("g");
2.執行編譯: javac StringAdd.java
生成位元組碼StringAdd.class
3.反編譯:javap -c -l -verbose StringAdd > StringAdd.javap
生成反編譯檔案。
main方法如下(此處隻列出主要部分,完整檔案見附件):
0: ldc #2; //String abcd
#字元串常量連接配接,已經過常量折疊。
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 ab
12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: new #7; //class java/lang/String
18: dup
19: ldc #8; //String cd 。
21: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V
24: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: astore_2
# 通過構造方法建立的String常量沒有折疊,但字元串連接配接操作已被StringBuilder取代
31: ldc #11; //String ab1cd
33: astore_3
# 有類型轉換的常量字元串連接配接操作也被已常量折疊方式優化
34: new #3; //class java/lang/StringBuilder
37: dup
38: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
41: ldc #12; //String abc
43: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
46: new #7; //class java/lang/String
49: dup
50: ldc #13; //String d
52: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V
55: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
58: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
61: astore 4
# 同時包含字面常量和通過構造方法構造的String的連接配接,字面常量部分被已常量折疊方式優化,剩餘部分的字元串連接配接被StringBuilder取代。
63: new #14; //class java/lang/StringBuffer
66: dup
67: ldc #15; //String e
69: invokespecial #16; //Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
72: astore 5
74: aload 5
76: ldc #17; //String f
78: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
81: ldc #19; //String g
83: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
# 通過StringBuffer構造字元串沒有被優化。
86: pop
87: return
分析:
通過上面對反編譯位元組碼的分析,我們可以看到編譯器在編譯期對代碼的優化特點:
1 常量折疊,将編譯期可以計算出的靜态結果提前得出,将運作時計算開銷降低為0。
2 自動将字元串連接配接操作優化為StringBuilder類的append方法,以提高連接配接速度。
3 對于調用方法動态生成的對象無法以常量折疊方式優化
通過分析可以得知:
1 為了提高可讀性,将一個字元串常量分割為多個用于排版,不會影響運作效率;
2 為了提高可讀性,可以直接使用字元串連接配接符“+”,替換非線程同步的StringBuilder類,不會對運作時效率産生影響
3 使用單線程下使用StringBuffer反而會降低性能。猜測:1.5以前的版本可能沒有差別,或使用StringBuffer更快,因為1.5才開始有StringBuilder類,也不知道1.5以前的編譯器會不會使用StringBuffer替換字元串連接配接操作。
引申:
存在域定義
static final String a="a";
static String b="b";
static final String c=new String("c");
反編譯方法内部代碼
String name1 = "1"+a;
String name2 = "2"+b;
String name3 = "3"+c;
效果如下:
0: ldc #2; //String 1a
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 2
12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: getstatic #7; //Field b:Ljava/lang/String;
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_2
# 變量未被折疊
25: new #3; //class java/lang/StringBuilder
28: dup
29: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
32: ldc #9; //String 3
34: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: getstatic #10; //Field c:Ljava/lang/String;
40: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
46: astore_3
# 動态常量未被折疊儲存
分析:
1 常量可以被認為運作時不可改變,是以編譯時被以常量折疊方式優化。
2 變量和動态生成的常量必須在運作時确定值,是以不能在編譯期折疊優化
結論:
如class1 中某個地方直接引用了 class2中的某個final常量,則在編譯時會将常量值記入class1的常量池中,或被常量折疊優化。如果class2修改了這個常量并重新編譯,運作時class1中的值不會随之變動,而是使用舊的class2的值,導緻程式出現不可預期的效果。
是以建議 通過動态指派方式給常量指派,如:
final String str = new String(“str1”);
final int one = new Integer(1);
雖然增加了類初始化的時間,但可以保證final值所在class檔案更新後其他class不用重新編譯就可以使用新的值。(雖然随便修改final定義是不好的。。。)
附件:
用到以下2個類。
public class StringAdd{
public static void main(String[] arg){
String name = "ab"+"cd";
String name1 = "ab" + new String("cd");
String name2 = "ab"+1+"cd";
String name3 = "ab"+"c" + new String("d");
StringBuffer name4 = new StringBuffer("e");
name4.append("f").append("g");
}
}
public class StringAdd2{
static final String a="a";
static String b="b";
static final String c=new String("c");
public static void main(String[] args){
String name1 = "1"+a;
String name2 = "2"+b;
String name3 = "3"+c;
}
}
反編譯結果如下:
Compiled from "StringAdd.java"
public class StringAdd extends java.lang.Object
SourceFile: "StringAdd.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #21.#30; // java/lang/Object."<init>":()V
const #2 = String #31; // abcd
const #3 = class #32; // java/lang/StringBuilder
const #4 = Method #3.#30; // java/lang/StringBuilder."<init>":()V
const #5 = String #33; // ab
const #6 = Method #3.#34; // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #7 = class #35; // java/lang/String
const #8 = String #36; // cd
const #9 = Method #7.#37; // java/lang/String."<init>":(Ljava/lang/String;)V
const #10 = Method #3.#38; // java/lang/StringBuilder.toString:()Ljava/lang/String;
const #11 = String #39; // ab1cd
const #12 = String #40; // abc
const #13 = String #41; // d
const #14 = class #42; // java/lang/StringBuffer
const #15 = String #43; // e
const #16 = Method #14.#37; // java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
const #17 = String #44; // f
const #18 = Method #14.#45; // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #19 = String #46; // g
const #20 = class #47; // StringAdd
const #21 = class #48; // java/lang/Object
const #22 = Asciz <init>;
const #23 = Asciz ()V;
const #24 = Asciz Code;
const #25 = Asciz LineNumberTable;
const #26 = Asciz main;
const #27 = Asciz ([Ljava/lang/String;)V;
const #28 = Asciz SourceFile;
const #29 = Asciz StringAdd.java;
const #30 = NameAndType #22:#23;// "<init>":()V
const #31 = Asciz abcd;
const #32 = Asciz java/lang/StringBuilder;
const #33 = Asciz ab;
const #34 = NameAndType #49:#50;// append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #35 = Asciz java/lang/String;
const #36 = Asciz cd;
const #37 = NameAndType #22:#51;// "<init>":(Ljava/lang/String;)V
const #38 = NameAndType #52:#53;// toString:()Ljava/lang/String;
const #39 = Asciz ab1cd;
const #40 = Asciz abc;
const #41 = Asciz d;
const #42 = Asciz java/lang/StringBuffer;
const #43 = Asciz e;
const #44 = Asciz f;
const #45 = NameAndType #49:#54;// append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #46 = Asciz g;
const #47 = Asciz StringAdd;
const #48 = Asciz java/lang/Object;
const #49 = Asciz append;
const #50 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuilder;;
const #51 = Asciz (Ljava/lang/String;)V;
const #52 = Asciz toString;
const #53 = Asciz ()Ljava/lang/String;;
const #54 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuffer;;
{
public StringAdd();
LineNumberTable:
line 1: 0
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
LineNumberTable:
line 3: 0
line 4: 3
line 5: 31
line 6: 34
line 7: 63
line 8: 74
line 9: 87
Code:
Stack=4, Locals=6, Args_size=1
0: ldc #2; //String abcd
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 ab
12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: new #7; //class java/lang/String
18: dup
19: ldc #8; //String cd
21: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V
24: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: astore_2
31: ldc #11; //String ab1cd
33: astore_3
34: new #3; //class java/lang/StringBuilder
37: dup
38: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
41: ldc #12; //String abc
43: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
46: new #7; //class java/lang/String
49: dup
50: ldc #13; //String d
52: invokespecial #9; //Method java/lang/String."<init>":(Ljava/lang/String;)V
55: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
58: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
61: astore 4
63: new #14; //class java/lang/StringBuffer
66: dup
67: ldc #15; //String e
69: invokespecial #16; //Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
72: astore 5
74: aload 5
76: ldc #17; //String f
78: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
81: ldc #19; //String g
83: invokevirtual #18; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
86: pop
87: return
LineNumberTable:
line 3: 0
line 4: 3
line 5: 31
line 6: 34
line 7: 63
line 8: 74
line 9: 87
}
Compiled from "StringAdd2.java"
public class StringAdd2 extends java.lang.Object
SourceFile: "StringAdd2.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #16.#32; // java/lang/Object."<init>":()V
const #2 = String #33; // 1a
const #3 = class #34; // java/lang/StringBuilder
const #4 = Method #3.#32; // java/lang/StringBuilder."<init>":()V
const #5 = String #35; // 2
const #6 = Method #3.#36; // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #7 = Field #15.#37; // StringAdd2.b:Ljava/lang/String;
const #8 = Method #3.#38; // java/lang/StringBuilder.toString:()Ljava/lang/String;
const #9 = String #39; // 3
const #10 = Field #15.#40; // StringAdd2.c:Ljava/lang/String;
const #11 = String #21; // b
const #12 = class #41; // java/lang/String
const #13 = String #22; // c
const #14 = Method #12.#42; // java/lang/String."<init>":(Ljava/lang/String;)V
const #15 = class #43; // StringAdd2
const #16 = class #44; // java/lang/Object
const #17 = Asciz a;
const #18 = Asciz Ljava/lang/String;;
const #19 = Asciz ConstantValue;
const #20 = String #17; // a
const #21 = Asciz b;
const #22 = Asciz c;
const #23 = Asciz <init>;
const #24 = Asciz ()V;
const #25 = Asciz Code;
const #26 = Asciz LineNumberTable;
const #27 = Asciz main;
const #28 = Asciz ([Ljava/lang/String;)V;
const #29 = Asciz <clinit>;
const #30 = Asciz SourceFile;
const #31 = Asciz StringAdd2.java;
const #32 = NameAndType #23:#24;// "<init>":()V
const #33 = Asciz 1a;
const #34 = Asciz java/lang/StringBuilder;
const #35 = Asciz 2;
const #36 = NameAndType #45:#46;// append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #37 = NameAndType #21:#18;// b:Ljava/lang/String;
const #38 = NameAndType #47:#48;// toString:()Ljava/lang/String;
const #39 = Asciz 3;
const #40 = NameAndType #22:#18;// c:Ljava/lang/String;
const #41 = Asciz java/lang/String;
const #42 = NameAndType #23:#49;// "<init>":(Ljava/lang/String;)V
const #43 = Asciz StringAdd2;
const #44 = Asciz java/lang/Object;
const #45 = Asciz append;
const #46 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuilder;;
const #47 = Asciz toString;
const #48 = Asciz ()Ljava/lang/String;;
const #49 = Asciz (Ljava/lang/String;)V;
{
static final java.lang.String a;
Constant value: String a
static java.lang.String b;
static final java.lang.String c;
public StringAdd2();
LineNumberTable:
line 1: 0
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
LineNumberTable:
line 6: 0
line 7: 3
line 8: 25
line 9: 47
Code:
Stack=2, Locals=4, Args_size=1
0: ldc #2; //String 1a
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 2
12: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: getstatic #7; //Field b:Ljava/lang/String;
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_2
25: new #3; //class java/lang/StringBuilder
28: dup
29: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
32: ldc #9; //String 3
34: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
37: getstatic #10; //Field c:Ljava/lang/String;
40: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
46: astore_3
47: return
LineNumberTable:
line 6: 0
line 7: 3
line 8: 25
line 9: 47
static {};
LineNumberTable:
line 3: 0
line 4: 5
Code:
Stack=3, Locals=0, Args_size=0
0: ldc #11; //String b
2: putstatic #7; //Field b:Ljava/lang/String;
5: new #12; //class java/lang/String
8: dup
9: ldc #13; //String c
11: invokespecial #14; //Method java/lang/String."<init>":(Ljava/lang/String;)V
14: putstatic #10; //Field c:Ljava/lang/String;
17: return
LineNumberTable:
line 3: 0
line 4: 5
}