Java:爲何循環體內的字符串拼接值得更改成StringBuilder.append?

兩個代碼例子:

  1. 例1:
    public static void main(String[] args) {
    	String testStr = "test";
    	String rst = testStr + 1 + "a" + "pig" + 2;
    	System.out.println(rst);
    }
    複製代碼
  2. 例2:
    public static void main(String[] args) {
    	String testStr = "";
    	for (int i = 0; i < 10; i++) {
    		testStr += i + "test";
    	}
    	System.out.println(testStr);
    }
    複製代碼

分析:

  1. 用javap反編譯例1中代碼的class文件獲得以下字節碼錶示:
    public static Method main:"([Ljava/lang/String;)V"
    stack 3 locals 3
    {
    	ldc	String "test";
    	astore_1;
    	new	class java/lang/StringBuilder;
    	dup;
    	aload_1;
    	invokestatic	Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;";
    	invokespecial	Method java/lang/StringBuilder."<init>":"(Ljava/lang/String;)V";
    	iconst_1;
    	invokevirtual	Method java/lang/StringBuilder.append:"(I)Ljava/lang/StringBuilder;";
    	ldc	String "a";
    	invokevirtual	Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;";
    	ldc	String "pig";
    	invokevirtual	Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;";
    	iconst_2;
    	invokevirtual	Method java/lang/StringBuilder.append:"(I)Ljava/lang/StringBuilder;";
    	invokevirtual	Method java/lang/StringBuilder.toString:"()Ljava/lang/String;";
    	astore_2;
    	getstatic	Field java/lang/System.out:"Ljava/io/PrintStream;";
    	aload_2;
    	invokevirtual	Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
    	return;
    	
    }
    複製代碼
    可見javac自動將這種連續的字符串拼接編譯成了StringBuilder.append,所以寫代碼時不用本身刻意寫成StringBuilder.append形式;
  2. 用javap反編譯例2中代碼的class文件獲得以下字節碼錶示:
    public static Method main:"([Ljava/lang/String;)V"
    stack 3 locals 3
    {
    		ldc	String "";              // 棧上來個空字符串
    		astore_1;                       // 棧頂元素彈出,放到局部變量1
    		iconst_0;                       // 棧上來個0
    		istore_2;                       // 棧頂元素彈出,放到局部變量2
    		goto	L35;                    // 跳轉到35行,此時棧空
    	L8:	stack_frame_type append;
    		locals_map class java/lang/String, int;
    		new	class java/lang/StringBuilder;                                                    // 棧上來個StringBuilder對象
    		dup;                                                                                      // 複製棧頂對象,也壓入棧中(StringBuilder對象)
    		aload_1;                                                                                  // 局部變量1壓入棧中
    		invokestatic	Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;"; // 棧頂元素強轉String
    		invokespecial	Method java/lang/StringBuilder."<init>":"(Ljava/lang/String;)V";          // 彈出棧頂2個元素,調用StringBuilder構造函數,剩下一個剛纔複製的、如今已經初始化好的StringBuilder在棧頂
    		iload_2;                                                                                  // 局部變量2壓入棧中:數字0
    		invokevirtual	Method java/lang/StringBuilder.append:"(I)Ljava/lang/StringBuilder;";     // 數字0 append進棧頂StringBuilder,返回值StringBuilder放回棧頂
    		ldc	String "test";                                                                    // 常量"test"壓入棧中
    		invokevirtual	Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;"; // 「test」append進棧頂StringBuilder,返回值StringBuilder放回棧頂
    		invokevirtual	Method java/lang/StringBuilder.toString:"()Ljava/lang/String;";                        // 棧頂StringBuilder彈出,toString,String結果放入棧頂
    		astore_1;                                                                                              // String結果彈出放進局部變量1
    		iinc	2, 1;                                                                                          // 2號局部變量++
    	L35:	stack_frame_type same;
    		iload_2;                                                            // 2號局部變量放到棧頂
    		bipush	10;                                                         // 常量10壓棧,做爲棧頂
    		if_icmplt	L8;                                                 // 彈出棧中前兩個數,若是棧中第二個數比棧頂元素小,就跳到第8行,不然繼續往下
    		getstatic	Field java/lang/System.out:"Ljava/io/PrintStream;"; // PrintStream對象壓入棧頂
    		aload_1;                                                                    // 局部變量1壓入棧頂(字符串)
    		invokevirtual	Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; // 彈出棧中2個數,以棧中第二個元素做爲參數1(即this),棧頂元素做爲參數2,調用println方法
    		return;
    	
    }
    複製代碼
    有點長,我稍微加了些註釋以便閱讀:可見javac仍然自動將循環體內的字符串拼接自動編譯成了StringBuilder.append,可是請注意,new class java/lang/StringBuilder; 這條指令出如今了L8標籤和L35後面的if_icmplt L8;跳轉指令之間,也就是說:它在循環體的內部 —— again也就是說 —— 這段代碼被優化成了每次循環都會從新new一個StringBuilder作字符串拼接,循環多少次、就會建立多少個StringBuilder對象;雖說這可能並不是什麼大不了的開銷,但仍然仍是程序員手工把StringBuilder寫在循環體外面、而後循環體內僅僅使用append更划算。

總結

因爲現代的javac編譯器可以自動將字符串拼接編譯爲StringBuilder.append的形式,所以平時直接書寫'+'號拼接便可,但若須要經過一個循環來拼接字符串,則最好將StringBuilder顯式地在循環體外建立再在循環體內使用,以免屢次重複建立StringBuilder對象java

相關文章
相關標籤/搜索