如何在 Java 9 中实现字符串连接?

JEP 280: Indify String Concatentation 中所述

更改 生成的静态串联字节码序列以使用对 JDK 库函数的调用。这将实现串联的未来优化,而无需进一步更改 由 施加的字节码。StringjavacinvokedynamicStringjavac

在这里,我想了解调用的用途是什么,以及字节码串联与?invokedynamicinvokedynamic


答案 1

“旧”方式输出一堆面向操作。请考虑以下程序:StringBuilder

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

如果我们使用JDK 8或更早版本编译它,然后用于查看字节码,我们会看到如下内容:javap -c Example

public class Example {
  public Example();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: aload_0
       8: iconst_0
       9: aaload
      10: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      13: ldc           #5                  // String -
      15: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      18: aload_0
      19: iconst_1
      20: aaload
      21: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: ldc           #5                  // String -
      26: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      29: aload_0
      30: iconst_2
      31: aaload
      32: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      38: astore_1
      39: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      42: aload_1
      43: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      46: return
}

如您所见,它创建了一个 并使用 .这是众所周知的效率相当低下,因为内置缓冲区的默认容量只有16个字符,并且编译器无法提前知道分配更多,因此最终不得不重新分配。它也是一堆方法调用。(请注意,JVM有时可以检测并重写这些调用模式,以使其更加高效。StringBuilderappendStringBuilder

让我们来看看Java 9生成了什么:

public class Example {
  public Example();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aload_0
       1: iconst_0
       2: aaload
       3: aload_0
       4: iconst_1
       5: aaload
       6: aload_0
       7: iconst_2
       8: aaload
       9: invokedynamic #2,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      14: astore_1
      15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: aload_1
      19: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      22: return
}

哦,我的,但那更短。:-)它从StringConcatFactory进行了一次调用,StringConcatFactory在其Javadoc中说:makeConcatWithConstants

促进创建字符串串联方法的方法,这些方法可用于有效地连接已知类型的已知数量的参数,可能是在类型适应和参数的部分评估之后。这些方法通常用作调用站点的引导方法,以支持 Java 编程语言的字符串串联功能。invokedynamic


答案 2

在详细介绍用于优化字符串串联的实现之前,在我看来,必须了解什么是调用动力学以及我如何使用它?invokedynamic

invokedynamic 指令简化并可能改进 JVM 上动态语言的编译器和运行时系统的实现。它通过允许语言实现者使用涉及以下步骤的指令定义自定义链接行为来实现此目的。invokedynamic


我可能会尝试带您了解这些为实现字符串串联优化而带来的更改。

  • 定义 Bootstrap 方法:- 在 Java9 中,用于调用站点的引导方法,主要支持字符串串联,主要是 makeConcatmakeConcatWithConstantsStringConcatFactory 实现一起引入的。invokedynamic

    使用 invokedynamic 提供了一种在运行时之前选择转换策略的替代方法。中使用的转换策略类似于之前 java 版本中引入的 LambdaMetafactory。此外,问题中提到的JEP的目标之一是进一步扩展这些策略。StringConcatFactory

  • 指定常量池条目:- 这些是指令的附加静态参数,而不是 (1) MethodHandles.Lookup 对象,它是用于在指令上下文中创建方法句柄的工厂,(2) 对象,动态调用站点中提到的方法名称和 (3) MethodType 对象,动态调用站点的解析类型签名。invokedynamicinvokedynamicString

    在代码的链接期间已经链接了。在运行时,bootstrap 方法运行并在执行串联的实际代码中链接。它使用适当的调用重写调用。这会从常量池加载常量字符串,利用 bootstrap 方法静态参数将这些常量和其他常量直接传递给 bootstrap 方法调用。invokedynamicinvokestatic

  • 使用 invokedynamic 指令:- 这为惰性链接提供了工具,方法是在初始调用期间提供一次引导调用目标的方法。这里优化的具体想法是将整个StringBuilder.append dance替换为对java.lang.invoke.StringConcatFactory的简单调用该调用将接受需要串联的值。

Indify String Concatenation提案通过一个示例说明了使用Java9对应用程序进行基准测试,其中编译了@T.J. Crowder共享的类似方法,并且在不同实现之间字节码的差异相当明显。