给定以下两个代码片段:
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < 5; i++) {
sb.append("hello ");
}
System.out.println(sb.toString());
和
String ss = "";
for (int i = 0; i < 5; i++) {
ss += "hello ";
}
System.out.println(ss);
我读到使用+
的字符串连接,使用StringBuffer
或StringBuilder
的append
方法。据我所知,StringBuilder
是可变的,所以任何修改都不应该创建一个新的实例。String
是不可变的,所以我期望循环中的连接每次都应该创建一个新实例(这里是5次)。但是,如果+
正在使用StringBuffer
或StringBuilder
的append
方法,它是否真的在每次循环中创建ss
的新实例?另外,哪些代码片段会更有效?
过去有些 Java字节码编译器会将String连接操作符(+
)编译为对临时StringBuilder
的操作。
然而:
- 这是一个实现细节。虽然JLS(在某些版本1中)说它可以以这种方式优化,但它从未说过它应该是。
- 这不是最近的OpenJDK Java编译器如何处理这个。连接表达式现在由字节码编译器编译为
invokedynamic
调用…然后由JIT编译器进行优化。
然而,您的示例涉及循环中的连接。我不认为当前Java版本中的JIT编译器可以跨多个循环迭代进行优化。
但是,这也可能会改变。的确,JEP 280暗示了"个性化"。字节码编译器对字符串连接的支持将使JIT编译器进一步优化。该可以包括循环的优化,如您的示例的第二个版本。如果它确实发生了变化,"手动优化"。在源代码中使用StringBuilder
调用可能会干扰JIT编译器的优化能力。
我的建议:避免过早/不必要的优化。除非您的应用程序级分析告诉您特定的连接序列是一个重要的瓶颈,否则不要管它。
1 -例如,Java 18 JLS说:"实现可以选择在一步中执行转换和连接,以避免创建然后丢弃中间字符串对象。为了提高重复字符串连接的性能,Java编译器可以使用StringBuffer类或类似的技术来减少通过计算表达式创建的中间字符串对象的数量。JLS 15.18.1.
我总是使用StringBuilder
在循环中连接字符串。这样做有几个好处:
- 你正在使用
StringBuilder
做什么,所以它更容易阅读(但不那么简洁); - 您可以使用方法参数传递
StringBuilder
(如果您使用不可变的String
,您只能将其用作返回值); StringBuilder
有额外的选项来改变字符串,如果这是必要的;- 它将提前分配一些缓冲空间,以便随后的更改/连接不会占用那么多内存;当涉及到字符串连接时,无论当前JVM采取哪个方向,都可以认为它是相对高性能的。
当然,我会使用new StringBuilder()
而不是new StringBuilder("")
,这没有意义-StringBuilder
实例已经以空字符串(和16个字符的缓冲区空间)开始。
如果你事先知道字符串的大小,那么创建一个足够大的缓冲区来容纳它是有意义的,所以它不需要请求更大的区域来容纳字符。内存管理相对昂贵。
关于这一点,请注意StringBuilder
实现了CharSequence
,所以如果你想避免复制,你可以在某些方法中使用它而不是String
。
Concatenate"将两个特定项连接在一起,而"追加"将您指定的内容添加到可能已经存在的内容