Java8 的 String Concatenate 比 StringBuilder Apporaches 慢?
- 2020 年 3 月 4 日
- 筆記
問題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 慢的原因。