使用 + 符号连接字符串
String concatenation with the + symbol
今天在看Antonio's Blog about toString() performance,有一段:
What used to be considered evil yesterday (“do not concatenate Strings with + !!!“), has become cool and efficient! Today the JVM compiles the + symbol into a string builder (in most cases). So, do not hesitate, use it.
现在我很困惑,因为他说 今天 JVM 将 + 符号编译成字符串生成器(在大多数情况下),但我从未听说过或看到(代码) 之前有过这样的事情。
有人可以举例说明吗JVM 在什么情况下执行此操作以及在什么条件下执行此操作?
规则
“do not concatenate Strings with + !!!“
是错误的,因为它不完整,因此具有误导性。
规则是
do not concatenate Strings with + in a loop
并且该规则仍然有效。原始规则从未打算在循环之外应用!
一个简单的循环
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);
还有声明
Today the JVM compiles the + symbol into a string builder (in most cases).
至少是误导性的,因为这个翻译已经用 Java 1.0 完成了(好吧,不是用 StringBuilder,而是用 StringBuffer,因为 StringBuilder 只是用 Java5 添加的)。
有人还可以争辩说
Today the JVM compiles the + symbol into a string builder (in most cases).
根本就是错误的,因为编译不是由JVM完成的。它由 Java 编译器完成。
对于问题:Java 编译器何时使用 StringBuilder.append()
以及何时使用其他机制?
Java 编译器(版本 1.8)的源代码包含两个地方,其中通过 +
运算符处理字符串连接。
- 第一名是字符串常量折叠(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/com/sun/tools/javac/comp/ConstFold.java?av=f#314)。在这种情况下,编译器可以计算结果字符串并使用结果字符串。
- 第二个位置是编译器创建赋值操作代码的位置 (http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/com/sun/tools/javac/jvm/Gen.java?av=f#2056)。在这种情况下,编译器总是发出代码来创建
StringBuilder
结论是,对于来自 OpenJDK 的 Java 编译器(这意味着由 Oracle 分发的编译器),短语 在大多数情况下 表示 总是。 (虽然这可能会随着 Java 9 发生变化,或者可能是另一个 Java 编译器,如 Eclipse 中包含的编译器使用其他机制)。
当您使用 + 运算符连接字符串时,编译器会将连接代码转换为使用 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");
这个确切形式的声明是错误的,它符合链接博客继续写废话的图片,就像你必须用 Objects.toString(…)
包装引用来处理 null
,例如"att1='" + Objects.toString(att1) + '\''
而不仅仅是 "att1='" + att1 + '\''
。没有必要这样做,显然,作者从未重新检查这些声明。
JVM 不负责编译 +
运算符,因为此运算符只是一个源代码工件。它是编译器,例如javac
这是负责任的,虽然没有关于编译形式的保证,但鼓励编译器通过 Java Language Specification:
使用构建器
An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
请注意,即使编译器不执行此优化,在字节码级别上仍然没有 +
运算符,因此编译器必须选择一个操作,JVM 理解,例如使用 String.concat
,这可能比使用 StringBuilder
更快,因为您只是恰好连接两个字符串。
即使假设字符串连接的最差编译策略(仍在规范内),说永远不要使用 +
连接字符串也是错误的,因为当您定义编译时间常量时,使用 +
是唯一的选择,当然,编译时常量通常比在运行时使用 StringBuilder
更有效。
实际上,应用于非常量字符串的 +
运算符在 Java 5 之前编译为 StringBuffer
用法,在 [=55] 中编译为 StringBuilder
用法=] 5 到 Java 8。当编译后的代码与 StringBuffer
的手动用法相同时。 StringBuilder
,不能有性能差异。
十多年前向 Java 5 的过渡是第一次,通过 +
的字符串连接明显优于手动 StringBuffer
使用,因为简单重新编译连接代码使其在内部使用可能更快的 StringBuilder
,而手动处理 StringBuffer
的代码需要重写以使用 StringBuilder
,该版本已引入该代码。
同样,Java 9 将使用 invokedynamic
指令编译字符串连接,允许 JRE 将其绑定到执行操作的实际代码,包括普通 [=55 中不可能的优化=] 代码。所以只需要重新编译字符串连接代码就可以得到这个特性,而没有等效的手动用法。
也就是说,虽然前提是错误的,即字符串连接从未被认为是邪恶的,但建议是正确的,请毫不犹豫地使用它。
只有少数情况下您确实可以通过手动处理缓冲区来提高性能,即当您需要较大的初始容量或在循环中连接很多内容时 并且该代码已被确定为分析工具的实际性能瓶颈…
Holger 的评论是正确的,在 java-9 +
中,字符串连接将从 StringBuilder
更改为 JRE 通过 invokedynamic
。在jdk-9中String concatenation
有6种可能的策略:
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
}
而默认的 不是 使用 StringBuilder,它是 MH_INLINE_SIZED_EXACT
。实现的工作方式实际上非常疯狂,并且它正在努力进行高度优化。
所以,据我所知,那里的建议是不好的。顺便说一下,这是 jdk 由 Aleksey Shipilev 投入的主要工作。他还在 jdk-9 中对字符串内部结构进行了重大更改,因为它们现在由 byte[]
而不是 char[]
支持。这是必需的,因为 ISO_LATIN_1
字符串可以编码为单个字节(一个字符 - 一个字节),因此少了很多 space。
今天在看Antonio's Blog about toString() performance,有一段:
What used to be considered evil yesterday (“do not concatenate Strings with + !!!“), has become cool and efficient! Today the JVM compiles the + symbol into a string builder (in most cases). So, do not hesitate, use it.
现在我很困惑,因为他说 今天 JVM 将 + 符号编译成字符串生成器(在大多数情况下),但我从未听说过或看到(代码) 之前有过这样的事情。
有人可以举例说明吗JVM 在什么情况下执行此操作以及在什么条件下执行此操作?
规则
“do not concatenate Strings with + !!!“
是错误的,因为它不完整,因此具有误导性。
规则是
do not concatenate Strings with + in a loop
并且该规则仍然有效。原始规则从未打算在循环之外应用!
一个简单的循环
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);
还有声明
Today the JVM compiles the + symbol into a string builder (in most cases).
至少是误导性的,因为这个翻译已经用 Java 1.0 完成了(好吧,不是用 StringBuilder,而是用 StringBuffer,因为 StringBuilder 只是用 Java5 添加的)。
有人还可以争辩说
Today the JVM compiles the + symbol into a string builder (in most cases).
根本就是错误的,因为编译不是由JVM完成的。它由 Java 编译器完成。
对于问题:Java 编译器何时使用 StringBuilder.append()
以及何时使用其他机制?
Java 编译器(版本 1.8)的源代码包含两个地方,其中通过 +
运算符处理字符串连接。
- 第一名是字符串常量折叠(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/com/sun/tools/javac/comp/ConstFold.java?av=f#314)。在这种情况下,编译器可以计算结果字符串并使用结果字符串。
- 第二个位置是编译器创建赋值操作代码的位置 (http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/com/sun/tools/javac/jvm/Gen.java?av=f#2056)。在这种情况下,编译器总是发出代码来创建
StringBuilder
结论是,对于来自 OpenJDK 的 Java 编译器(这意味着由 Oracle 分发的编译器),短语 在大多数情况下 表示 总是。 (虽然这可能会随着 Java 9 发生变化,或者可能是另一个 Java 编译器,如 Eclipse 中包含的编译器使用其他机制)。
当您使用 + 运算符连接字符串时,编译器会将连接代码转换为使用 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");
这个确切形式的声明是错误的,它符合链接博客继续写废话的图片,就像你必须用 Objects.toString(…)
包装引用来处理 null
,例如"att1='" + Objects.toString(att1) + '\''
而不仅仅是 "att1='" + att1 + '\''
。没有必要这样做,显然,作者从未重新检查这些声明。
JVM 不负责编译 +
运算符,因为此运算符只是一个源代码工件。它是编译器,例如javac
这是负责任的,虽然没有关于编译形式的保证,但鼓励编译器通过 Java Language Specification:
An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
请注意,即使编译器不执行此优化,在字节码级别上仍然没有 +
运算符,因此编译器必须选择一个操作,JVM 理解,例如使用 String.concat
,这可能比使用 StringBuilder
更快,因为您只是恰好连接两个字符串。
即使假设字符串连接的最差编译策略(仍在规范内),说永远不要使用 +
连接字符串也是错误的,因为当您定义编译时间常量时,使用 +
是唯一的选择,当然,编译时常量通常比在运行时使用 StringBuilder
更有效。
实际上,应用于非常量字符串的 +
运算符在 Java 5 之前编译为 StringBuffer
用法,在 [=55] 中编译为 StringBuilder
用法=] 5 到 Java 8。当编译后的代码与 StringBuffer
的手动用法相同时。 StringBuilder
,不能有性能差异。
十多年前向 Java 5 的过渡是第一次,通过 +
的字符串连接明显优于手动 StringBuffer
使用,因为简单重新编译连接代码使其在内部使用可能更快的 StringBuilder
,而手动处理 StringBuffer
的代码需要重写以使用 StringBuilder
,该版本已引入该代码。
同样,Java 9 将使用 invokedynamic
指令编译字符串连接,允许 JRE 将其绑定到执行操作的实际代码,包括普通 [=55 中不可能的优化=] 代码。所以只需要重新编译字符串连接代码就可以得到这个特性,而没有等效的手动用法。
也就是说,虽然前提是错误的,即字符串连接从未被认为是邪恶的,但建议是正确的,请毫不犹豫地使用它。
只有少数情况下您确实可以通过手动处理缓冲区来提高性能,即当您需要较大的初始容量或在循环中连接很多内容时 并且该代码已被确定为分析工具的实际性能瓶颈…
Holger 的评论是正确的,在 java-9 +
中,字符串连接将从 StringBuilder
更改为 JRE 通过 invokedynamic
。在jdk-9中String concatenation
有6种可能的策略:
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
}
而默认的 不是 使用 StringBuilder,它是 MH_INLINE_SIZED_EXACT
。实现的工作方式实际上非常疯狂,并且它正在努力进行高度优化。
所以,据我所知,那里的建议是不好的。顺便说一下,这是 jdk 由 Aleksey Shipilev 投入的主要工作。他还在 jdk-9 中对字符串内部结构进行了重大更改,因为它们现在由 byte[]
而不是 char[]
支持。这是必需的,因为 ISO_LATIN_1
字符串可以编码为单个字节(一个字符 - 一个字节),因此少了很多 space。