使用+符号进行字符串连接

今天我正在阅读Antonio的博客关于toString()的表现 ,还有一段:

曾经被认为是邪恶的昨天(“不要用+ !!!连接字符串”),已经变得很酷和高效! 今天,JVM将+符号编译为字符串构建器(在大多数情况下) 。 所以,不要犹豫,使用它。

现在我很困惑,因为他说今天JVM将+符号编译成字符串构建器(在大多数情况下) ,但我以前从未听过或看到(代码)这样​​的东西。

有人可以举例说明JVM是做什么的,它会在什么条件下发生

规则

“不要用+ !!!连接字符串”

是错的,因为它不完整,因此具有误导性。

规则是

不要在循环中将字符串与+连接

而这条规则仍然有效。 原始规则从未打算在循环之外应用!

一个简单的循环

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编译器完成。


对于这个问题:Java编译器何时使用StringBuilder.append() ,何时使用其他机制?

Java编译器(版本1.8)的源代码包含两个地方,通过+运算符处理String concation。

结论是,对于来自OpenJDK的Java编译器(这意味着由Oracle分发的编译器), 在大多数情况下,该短语总是意味着。 (虽然这可能会随着Java 9而改变,或者可能是另一个像Eclipse中包含的Java编译器使用其他机制的Java编译器)。

这个语句在这个确切的forms中是错误的,它符合链接博客继续写废话的图片,就像你必须用Objects.toString(…)包装引用来处理null ,例如"att1='" + Objects.toString(att1) + '\''而不仅仅是"att1='" + att1 + '\'' 。 没有必要这样做,显然,作者从未重新检查过这些说法。

JVM不负责编译+运算符,因为此运算符仅是源代码工件。 它是编译器,例如负责的javac ,虽然没有关于编译forms的保证,但鼓励编译器使用Java语言规范的构建器:

实现可以选择在一个步骤中执行转换和连接,以避免创建然后丢弃中间String对象。 为了提高重复字符串连接的性能,Java编译器可以使用StringBuffer类或类似技术来减少通过表达式求值创建的中间String对象的数量。

请注意,即使编译器没有执行此优化,在字节代码级别上仍然没有+运算符这样的东西,因此编译器必须选择一个操作,JVM理解,例如使用String.concat ,这可能是在你只是连接两个字符串的情况下,比使用StringBuilder更快。

即使假设字符串连接的编译策略最差(仍然在规范范围内),说永远不要用+连接字符串是错误的,因为当你定义编译时常量时,使用+是唯一的选择,当然,编译时常量通常比在运行时使用StringBuilder更有效。

实际上,应用于非常量字符串的+运算符在Java 5之前被编译为StringBuilder用法,在Java 5到Java 8中被编译为StringBuffer 。当编译的代码与StringBuffer的手动使用相同时。 StringBuilder ,不可能有性能差异。

十多年前转换到Java 5,这是第一次,字符串连接通过+明显胜过手动StringBuffer使用,因为简单地重新编译连接代码使它在内部使用可能更快的StringBuilder ,而手动代码需要重写处理StringBuffer以使用已在该版本中引入的StringBuilder

同样,Java 9将使用invokedynamic指令编译字符串连接,允许JRE将其绑定到执行操作的实际代码,包括普通Java代码中不可能的优化。 因此,只需重新编译字符串连接代码即可获得此function,而没有相应的手动用法。

也就是说,虽然前提是错误的,即字符串连接从未被视为邪恶,但建议是正确的,请不要犹豫使用它。

只有少数情况下,您可以通过手动处理缓冲区来提高性能,即当您需要较大的初始容量或在循环中连接很多并且该代码已被分析工具识别为实际性能瓶颈时 ……

Holger在他的评论中是正确的,在java-9 + ,字符串连接将从StringBuilder变为JRE通过invokedynamic 选择的策略 。 在jdk-9中,有6种策略可用于String concatenation

  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 。 实施起来实际上非常疯狂,并且它正在努力进行高度优化。

所以,据我所知,没有任何建议是不好的。 顺便说一句,这是由Aleksey Shipilev的jdk所付出的主要努力。 他还在jdk-9中添加了对String内部的重大更改,因为它们现在由byte[]而不是char[] 。 这是因为ISO_LATIN_1字符串可以在一个字节(一个字符 – 一个字节)中编码,因此空间更少。

使用+运算符连接字符串时,编译器会将连接代码转换为使用StringBuffer以获得更好的性能。 为了提高性能, StringBuffer是更好的选择。

使用+运算符连接两个字符串的最快方法。

 String str = "Java"; str = str + "Tutorial"; 

编译器将此代码转换为:

 String s1 = "Java"; StringBuffer sb = new StringBuffer(s1); sb.append("Tutorial"); s1 = sb.toString(); 

因此最好使用StringBufferString.format进行连接

使用String.format

 String s = String.format("%s %s", "Java", "Tutorial");