假设我们有一些代码如下:
public static void main(String[] args) { String s = ""; for(int i=0 ; i<10000 ; i++) { s += "really "; } s += "long string."; }
(是的,我知道更好的实现会使用a StringBuilder
,但请耐心等待.)
平凡地说,我们可能期望生成的字节码类似于以下内容:
public static void main(java.lang.String[]); Code: 0: ldc #2 // String 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: sipush 10000 9: if_icmpge 25 12: aload_1 13: ldc #3 // String really 15: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 18: astore_1 19: iinc 2, 1 22: goto 5 25: aload_1 26: ldc #5 // String long string. 28: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 31: astore_1 32: return
然而,相反,编译器试图变得更聪明 - 而不是使用concat方法,它在优化中使用了StringBuilder
对象,因此我们得到以下内容:
public static void main(java.lang.String[]); Code: 0: ldc #2 // String 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: sipush 10000 9: if_icmpge 38 12: new #3 // class java/lang/StringBuilder 15: dup 16: invokespecial #4 // Method java/lang/StringBuilder."":()V 19: aload_1 20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 23: ldc #6 // String really 25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 28: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 31: astore_1 32: iinc 2, 1 35: goto 5 38: new #3 // class java/lang/StringBuilder 41: dup 42: invokespecial #4 // Method java/lang/StringBuilder." ":()V 45: aload_1 46: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 49: ldc #8 // String long string. 51: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 54: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 57: astore_1 58: return
但是,这对我来说似乎反效果 - 而不是为整个循环使用一个字符串构建器,为每个单个连接操作创建一个,使其等效于以下内容:
public static void main(String[] args) { String s = ""; for(int i=0 ; i<10000 ; i++) { s = new StringBuilder().append(s).append("really ").toString(); } s = new StringBuilder().append(s).append("long string.").toString(); }
因此,现在不是创建大量字符串对象并抛弃它们的原始琐碎坏方法,编译器已经产生了一种更糟糕的方法,即创建大量String
对象,大量StringBuilder
对象,调用更多方法,并且仍然将它们全部丢弃生成与没有此优化时相同的输出.
所以问题必须是 - 为什么?我理解在这种情况下:
String s = getString1() + getString2() + getString3();
...编译器将为StringBuilder
所有三个字符串创建一个对象,因此有些情况下优化很有用.但是,检查字节码表明甚至将上述情况分为以下几种:
String s = getString1(); s += getString2(); s += getString3();
...意味着我们回到了StringBuilder
单独创建三个对象的情况.我知道这些是不是奇怪的角落情况,但以这种方式(并在循环中)附加到字符串是非常常见的操作.
当然,在编译时确定编译器生成的StringBuilder
只是附加了一个值是否是微不足道的- 如果是这种情况,请使用简单的concat操作?
这完全是8u5(然而,它可能会回到至少Java 5,可能之前.)FWIW,我的基准测试(不出所料)使手动concat()
方法比使用+=
10,000个元素的循环快2到3倍.当然,使用手册StringBuilder
始终是首选方法,但编译器肯定不会对+=
方法的性能产生负面影响吗?