Javac的StringBuilder优化弊大于利吗?
假设我们有一些代码,如下所示:
public static void main(String[] args) {
String s = "";
for(int i=0 ; i<10000 ; i++) {
s += "really ";
}
s += "long string.";
}
(是的,我知道一个更好的实现会使用 ,但请耐心等待。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."<init>":()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."<init>":()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
当然,在编译时确定编译器生成的是否只追加一个值是微不足道的 - 如果是这种情况,请使用简单的concat操作来代替?StringBuilder
这一切都与8u5有关(但是,它至少可以追溯到Java 5,可能是在之前。FWIW,我的基准测试(不出所料)使手动方法比在具有10,000个元素的循环中使用快2x3倍。当然,使用手册始终是首选方法,但编译器肯定也不会对方法的性能产生不利影响吗?concat()
+=
StringBuilder
+=