我知道使用“ +”串联运算符构建字符串效率很低,这就是为什么建议使用StringBuilder类的原因,但是我想知道这种模式是否也效率低下?
String some = a + "\t" + b + "\t" + c + "\t" + d + "\t" + e;
我猜这里编译器会优化分配,还是不会?
答案 0 :(得分:6)
此特定示例将由编译器内联:
String a = "a";
String b = "bb";
String c = "ccc";
String some = a + "\t" + b + "\t" + c;
Java 9+将使用invokedynamic with makeConcatWithConstants对其进行内联,以使其高效。根据{{1}}的输出:
javap -v
但是如果Code:
stack=3, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String bb
5: astore_2
6: ldc #4 // String ccc
8: astore_3
9: aload_1
10: aload_2
11: aload_3
12: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
17: astore 4
19: return
a
和b
是编译时常数,编译器将进一步优化代码:
c
和final String a = "a";
final String b = "bb";
final String c = "ccc";
String some = a + "\t" + b + "\t" + c;
将被加载一个恒定值:
some
在其他情况下,例如Code:
stack=1, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String bb
5: astore_2
6: ldc #4 // String ccc
8: astore_3
9: ldc #5 // String a\tbb\tccc
11: astore 4
13: return
循环编译器可能无法生成优化的代码,因此for
可能会更快。
答案 1 :(得分:4)
您的前提“使用“ +”串联运算符构建字符串的效率非常低“,这是不正确的。首先,字符串串联本身并不是一个便宜的操作,因为它意味着创建一个包含所有串联字符串的新字符串,因此需要复制字符内容。但这确实适用,无论您如何如何。
使用+
运算符时,您是在说您想做什么,而不是说如何做。甚至Java语言规范都不要求特定的实现策略,只是必须在编译时完成编译时常量的连接。因此,对于编译时常量,+
运算符 是最有效的解决方案¹。
实际上,从Java 5到Java 8的所有常用编译器都是在引擎盖下使用StringBuilder
生成代码的(在Java 5之前,他们使用StringBuffer
)。这适用于像您这样的语句,因此将其替换为手动StringBuilder
使用不会带来太大收益。通过提供合理的初始容量,您可能会比典型的编译器生成的代码要好一些,但仅此而已。
从Java 9开始,编译器生成一条invokedynamic
指令,该指令允许运行时提供执行连接的实际代码。这可能是StringBuilder
代码,与过去使用的代码相似,但也有一些完全不同的代码。最值得注意的是,运行时提供的代码可以访问特定于实现的功能,而应用程序代码则不能。因此,现在,通过+
进行的字符串连接甚至可以比基于StringBuilder
的代码更快。
因为这仅适用于单个串联表达式,所以当使用多个语句甚至一个循环执行字符串构造时,在整个构造过程中始终使用StringBuilder
可能比多个串联操作更快。但是,由于代码在优化的环境中运行,并且JVM可以识别其中的某些模式,所以甚至不能肯定地说。
这是记住旧规则的时候,只有在性能实际存在问题时才尝试优化性能。并始终使用公正的测量工具进行验证,即尝试进行的优化是否真的可以改善性能。关于性能优化技巧,有很多错误或过时的误解。
¹,除非您有重复的部分,并希望减小类文件的大小
答案 2 :(得分:2)
在一般情况下,使用+
并使用StringBuilder
进行字符串连接是绝对正确且可行的。但是在不同情况下,与+
的连接比使用StringBuilder
的连接效率低。
这具有良好的性能,因为JVM使用StringBuilder
对其进行了转换。
String some = a + "\t" + b + "\t" + c + "\t" + d + "\t" + e;
这可以,因为JVM在内部将以下代码更改为以下代码:
String some = new StringBuilder().append(a).append('\t').append(c).append('\t')
.append(d).append('\t').append(e).toString();
PS 。StringBuilder
具有内部缓冲区char[]
。如果您知道结果字符串将持续多长时间,则最好在开始时保留整个缓冲区。例如。如果最终字符串将最多1024个字符,那么您可以执行new StringBuilder(1024)
这会降低性能,因为JVM无法用一个StringBuilder
进行while循环,就像这样:
StringBuilder buf = new StringBuilder();
for (int i = 0; i < 10; i++)
buf.append(a).append('\t').append(c).append('\t')
.append(d).append('\t').append(e).append('t');
String some = buf.toString();
但是JVM仍然能够优化每次循环迭代中的所有串联;像这样:
String some = "";
for (int i = 0; i < 10; i++) {
some = new StringBuilder(some).append(a).append('\t').append(c).append('\t')
.append(d).append('\t').append(e).append('t');
}
如您所见,在循环中使用字符串串联存在一些缺点。