字符串串联是否有效?

Met*_*est 2 java string string-concatenation java-8

我知道使用"+"连接运算符来构建字符串是非常低效的,这就是为什么建议使用StringBuilder类,但我想知道这种模式是否也是低效的?

String some = a + "\t" + b + "\t" + c + "\t" + d + "\t" + e;
Run Code Online (Sandbox Code Playgroud)

我猜这里的编译器会优化编程,不是吗?

Kar*_*cki 6

这个特定的例子将由编译器内联:

String a = "a";
String b = "bb";
String c = "ccc";
String some = a + "\t" + b + "\t" + c;
Run Code Online (Sandbox Code Playgroud)

Java 9+将使用invokedynamic与makeConcatWithConstants进行内联,使其高效.根据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
Run Code Online (Sandbox Code Playgroud)

但是如果a bc编译时常量编译器将进一步优化代码:

final String a = "a";
final String b = "bb";
final String c = "ccc";
String some = a + "\t" + b + "\t" + c;
Run Code Online (Sandbox Code Playgroud)

并将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
Run Code Online (Sandbox Code Playgroud)

在其他情况下,例如for循环,编译器可能无法生成优化代码,因此StringBuilder可能更快.

  • 想重新强调一点,这只适用于文字/常量连接.如果`a`/etc是一个变量,比方说,传入,我们就失去了优化(并且它可能只是被一个`StringBuilder`调用替换,从我过去看到的). (3认同)

Hol*_*ger 6

您的前提“使用“+”连接运算符构建字符串的效率非常低”,这是不正确的。首先,字符串连接本身并不是一个廉价的操作,因为它意味着创建一个包含所有连接字符串的新字符串,因此需要复制字符内容。但这始终适用,无论您如何操作。

当您使用+运算符时,您是在告诉您想要做什么,而不是说如何去做。甚至 Java 语言规范也没有要求特定的实现策略,只是编译时常量的串联必须在编译时完成。因此,对于编译时常量,+运算符最有效的解决方案¹。

实际上,从 Java 5 到 Java 8 的所有常用编译器都在后台使用 生成代码StringBuilder(在 Java 5 之前,他们使用StringBuffer)。这适用于像您这样的语句,因此将其替换为手动StringBuilder使用不会有太大好处。通过提供合理的初始容量,您可能会比典型的编译器生成的代码稍好一些,但仅此而已。

从 Java 9 开始,编译器生成一条invokedynamic指令,允许运行时提供执行连接的实际代码。这可能是一个StringBuilder类似于过去使用的代码,但也完全不同。最值得注意的是,运行时提供的代码可以访问应用程序代码不能访问的实现特定功能。所以现在,通过字符串连接+可以比StringBuilder基于代码更快。

由于这仅适用于单个串联表达式,因此在使用多个语句甚至循环执行字符串构造时,StringBuilder在整个构造过程中始终使用 a可能比多个串联操作更快。但是,由于代码在优化环境中运行,JVM 可以识别其中一些模式,因此无法确定。

这是记住旧规则的时候,只有在性能出现实际问题时才尝试优化性能。并且始终使用公正的测量工具来验证尝试的优化是否真正提高了性能。关于性能优化技巧,有很多广为流传的神话,无论是错误的还是过时的。

¹ 除非您有重复的部分并希望减小类文件的大小

  • 谢谢你!我没有时间发布答案,因为我“真的”不喜欢接受的答案,而且我也不知道如何说在字节码级别有一个 `StringConcatFactory::makeConcatWithConstants` 实际上回答了它。 (2认同)