今天我正在阅读Antonio's Blog about toString() performance并且有一段:
昨天曾经被认为是邪恶的(“不要用+ !!!连接字符串”),已经变得很酷和高效! 今天,JVM将+符号编译为字符串构建器(在大多数情况下)。所以,不要犹豫,使用它。
现在我很困惑,因为他说今天JVM将+符号编译成字符串构建器(在大多数情况下),但我从来没有听过或看过(代码)之前的这样的事情。
有人可以举例说明 JVM在哪做以及它在什么条件下发生?
答案 0 :(得分:13)
规则
“不要用+ !!!连接字符串”
是错误的,因为它不完整,因此具有误导性。
规则是
不要在字符串中使用+ 连接字符串
并且该规则仍然有效。最初的规则从未打算在循环之外应用!
一个简单的循环
String s = "";
for (int i = 0; i < 10000; i++) { s += i; }
System.out.println(s);
仍然比
慢得多StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) { sb.append(i); }
System.out.println(sb.toString());
因为Java编译器必须将第一个循环转换为
String s = "";
for (int i = 0; i < 1000; i++) { s = new StringBuilder(s).append(i).toString(); }
System.out.println(s);
还有索赔
今天,JVM将+符号编译成字符串构建器(在大多数情况下)。
至少具有误导性,因为这个翻译已经完成了Java 1.0(好吧,不是使用StringBuilder而是使用StringBuffer,因为StringBuilder只是添加了Java5)。
人们也可以争辩说
今天,JVM将+符号编译成字符串构建器(在大多数情况下)。
是完全错误的,因为编译不是由JVM完成的。它由Java Compiler完成。
关于问题:Java编译器何时使用StringBuilder.append()
以及何时使用其他机制?
Java编译器的源代码(版本1.8)包含两个地方,通过+
运算符处理字符串分段。
StringBuilder
结论是,对于来自OpenJDK的Java编译器(这意味着由Oracle分发的编译器),在大多数情况下这个短语意味着始终。 (虽然这可能会随着Java 9而改变,或者可能是另一个像Eclipse中包含的Java编译器使用其他机制的Java编译器。)
答案 1 :(得分:5)
Holger在他的评论中是正确的,在java-9 +
中,字符串连接将从StringBuilder
更改为由JRE选择的策略通过{{ 1}}。 jdk-9中invokedynamic
有6种可能的策略:
String concatenation
使用StringBuilder的默认不是,它是 private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
。实施起来实际上非常疯狂,并且它正在努力进行高度优化。
所以,据我所知,没有任何建议是不好的。顺便说一句,这是由Aleksey Shipilev的jdk所付出的主要努力。他还在jdk-9中对String内部进行了重大更改,因为它们现在由MH_INLINE_SIZED_EXACT
而不是byte[]
支持。这需要因为char[]
字符串可以在一个字节(一个字符 - 一个字节)中编码,所以空间更少。
答案 2 :(得分:4)
这个语句在这个确切的形式中是错误的,并且它符合链接博客继续写废话的情况,就像你必须用Objects.toString(…)
包装引用来处理null
,例如"att1='" + Objects.toString(att1) + '\''
而非"att1='" + att1 + '\''
。没有必要这样做,显然,作者从未重新检查过这些说法。
JVM不负责编译+
运算符,因为此运算符仅是源代码工件。这是编译器,例如javac
负责,虽然对编译表格没有保证,但鼓励编制者使用Java Language Specification的建设者:
实现可以选择一步执行转换和连接,以避免创建然后丢弃中间String对象。为了提高重复字符串连接的性能,Java编译器可以使用StringBuffer类或类似技术来减少通过表达式求值创建的中间String对象的数量。
请注意,即使编译器没有执行此优化,在字节代码级别上仍然没有+
运算符这样的东西,因此编译器必须选择一个JVM理解的操作,例如:使用String.concat
,这可能比使用StringBuilder
更快,因为你只是连接了两个字符串。
即使假设字符串连接的编译策略最糟糕(仍在规范范围内),也不应该说永远不会将字符串与+
连接起来,就像定义编译时常量一样,使用{{1是唯一的选择,当然,编译时常量通常比在运行时使用+
更有效。
实际上,应用于非常量字符串的StringBuilder
运算符在Java 5之前被编译为+
用法,在Java 5到Java 8中被编译为StringBuffer
用法。代码与StringBuilder
resp的手动使用相同。 StringBuffer
,不会有性能差异。
十多年前向Java 5的过渡是第一次,通过StringBuilder
进行字符串连接明显胜过手动+
使用,只需重新编译连接代码就可以了在内部使用可能更快的StringBuffer
,而手动处理StringBuilder
的代码需要重写为使用StringBuffer
,该版本已在该版本中引入。
同样,Java 9将使用StringBuilder
指令编译字符串连接,允许JRE将其绑定到执行操作的实际代码,包括普通Java代码中不可能的优化。因此,只需重新编译字符串连接代码即可获得此功能,而没有相应的手动用法。
尽管如此,虽然前提是错误的,即字符串连接从未被视为邪恶,但建议是正确的,请不要犹豫使用它。
只有少数几种情况可以通过手动处理缓冲区来提高性能,即当您需要较大的初始容量或在循环中连接很多并且该代码已被确定为实际性能瓶颈时通过分析工具 ......
答案 3 :(得分:0)
使用+运算符连接字符串时,编译器会将连接代码转换为使用StringBuffer
以获得更好的性能。为了提高性能StringBuffer
是更好的选择。
使用+运算符连接两个字符串的最快方法。
String str = "Java";
str = str + "Tutorial";
编译器将此代码转换为:
String s1 = "Java";
StringBuffer sb = new StringBuffer(s1);
sb.append("Tutorial");
s1 = sb.toString();
因此最好使用StringBuffer
OR String.format
进行连接
使用String.format
String s = String.format("%s %s", "Java", "Tutorial");