Java8 的 String Concatenate 比 StringBuilder Apporaches 慢?

問題1: 文章說,大量 String + 連接比 通過 相應的StringBuilder 連接慢,要是在 Java7 之前我信,可以在 Java8 以及之後,編譯器自動幫助我們把 + 優化成 StringBuilder (StringBuffer) 了。難道 Java8 的 String Concatenate 比 StringBuilder (StringBuffer) 慢?帶着這樣的疑問,決定好好的親自試驗一番。

/**   * @author shengjk1   * @date 2020/1/4   */  public class Test {  	public static void main(String[] args) {  		int loopMax = 100000;    		long start = System.currentTimeMillis();  		String res = "";  		for (int i = 0; i < loopMax; i++) {  			res += i;  		}  		long end = System.currentTimeMillis();  		System.out.println((end - start));  		System.out.println("res" + res);    		StringBuilder builder = new StringBuilder();  		start = System.currentTimeMillis();  		res = "";  		builder.append(res);  		for (int i = 0; i < loopMax; i++) {  			builder.append(i);  		}  		end = System.currentTimeMillis();  		System.out.println((end - start));  		System.out.println("bulider" + builder.toString());  	}  }

res: 39733 ms bulider: 5 ms 注意是大量字符串的連接,特別是成為 熱代碼 之後,少量的字符串連接的差距就更顯現不出來了。

問題來了,明明在 Java8 中 編譯器將 String Concatenate 優化成了 StringBuilder ,為何差距還是這麼明顯?我們分別單獨編譯 String Concatenate 和 StringBuilder,然後分別單獨看一下它們對應的機器指令是什麼。

String Concatenate

       long start = System.currentTimeMillis();  		String res = "";  		for (int i = 0; i < loopMax; i++) {  			res += i;  		}  		long end = System.currentTimeMillis();  		System.out.println((end - start));  		System.out.println("res" + res);
 Code:         0: ldc           #2                  // int 100000         2: istore_1         3: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J         6: lstore_2         7: ldc           #4                  // String         9: astore        4        11: iconst_0        12: istore        5        #for 循環開始        14: iload         5        16: iload_1        17: if_icmpge     48        20: new           #5                  // class java/lang/StringBuilder        23: dup        24: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V        27: aload         4        29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        32: iload         5        34: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;        37: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        40: astore        4        42: iinc          5, 1        45: goto          14        # for 循環結束        # 在 for 循環結束和開始當中,每遍歷一次都會創建一個 StringBuilder 對象,與下面的代碼相比這就是速度慢的地方        48: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J        51: lstore        5        53: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;        56: lload         5        58: lload_2        59: lsub        60: invokevirtual #11                 // Method java/io/PrintStream.println:(J)V        63: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;        66: new           #5                  // class java/lang/StringBuilder        69: dup        70: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V        73: ldc           #12                 // String res        75: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        78: aload         4        80: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        83: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        86: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        89: return

StringBuilder

       StringBuilder builder = new StringBuilder();  		long start = System.currentTimeMillis();  		String res = "";  		builder.append(res);  		for (int i = 0; i < loopMax; i++) {  			builder.append(i);  		}  		long end = System.currentTimeMillis();  		System.out.println((end - start));  		System.out.println("bulider" + builder.toString());
 Code:         0: ldc           #2                  // int 100000         2: istore_1         3: new           #3                  // class java/lang/StringBuilder         6: dup         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V        10: astore_2        11: invokestatic  #5                  // Method java/lang/System.currentTimeMillis:()J        14: lstore_3        15: ldc           #6                  // String        17: astore        5        19: aload_2        20: aload         5        22: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        25: pop        26: iconst_0        27: istore        6         # for 循環開始        29: iload         6        31: iload_1        32: if_icmpge     48        35: aload_2        36: iload         6        38: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;        41: pop        42: iinc          6, 1        45: goto          29        # for 循環結束        48: invokestatic  #5                  // Method java/lang/System.currentTimeMillis:()J        51: lstore        6        53: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;        56: lload         6        58: lload_3        59: lsub        60: invokevirtual #10                 // Method java/io/PrintStream.println:(J)V        63: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;        66: new           #3                  // class java/lang/StringBuilder        69: dup        70: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V        73: ldc           #11                 // String bulider        75: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        78: aload_2        79: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        82: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        85: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        88: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        91: return

最關鍵的部分其實是 for 循環部分,仔細看一下就會發現,對於 String Concatenate 每循環一次都會創建一個 StringBuilder,並且會 append兩次然後 toString,並把結果賦值給 res,StringBuilder每次的創建和初始化也會浪費大量的時間以及內存。而 StringBuilder 僅僅創建一次,append 一次,toString 一次。

這也就解釋了明明編譯器自動幫助我們把 + 優化成 StringBuilder 了卻還是 比 StringBuilder 慢的原因。