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 慢的原因。