如何在Java 9中实现String连接?

时间:2017-10-01 13:34:27

标签: java string string-concatenation java-9 invokedynamic

正如JEP 280所述:

  

更改由String生成的静态javac - 连接字节码序列,以使用invokedynamic调用JDK库函数。这将使String级联的未来优化成为可能,而无需对javac所发送的字节码进行进一步更改。

在这里,我想了解invokedynamic调用的用途以及字节码连接与invokedynamic的不同之处?

3 个答案:

答案 0 :(得分:91)

“旧”方式输出一组面向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
}

如您所见,它会创建StringBuilder并使用append。这是着名的低效率,因为StringBuilder中内置缓冲区的默认容量只有16个字符,并且编译器无法预先分配更多,因此它最终不得不重新分配。它也是一堆方法调用。 (请注意,JVM可以有时检测并重写这些调用模式,以提高它们的效率。)

让我们看一下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 makeConcatWithConstants,就会在其Javadoc中说明这一点:

  

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

答案 1 :(得分:20)

在进入用于优化字符串连接的invokedynamic实现的详细信息之前,我认为必须获得What's invokedynamic and how do I use it?

的背景知识。
  

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

我可能会尝试使用为实现字符串连接优化而带来的更改来完成这些操作。

  • 定义Bootstrap方法: - 使用Java9,invokedynamic调用网站的引导方法,主要支持字符串连接makeConcatmakeConcatWithConstants,并引入了 StringConcatFactory 实施。

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

  • 指定常量池条目: - 这些是除{1} MethodHandles.Lookup对象之外的invokedynamic指令的附加静态参数,该对象是用于创建的工厂方法在invokedynamic指令的上下文中处理,(2)一个String对象,动态调用站点中提到的方法名称和(3)MethodType对象,已解析的类型签名动态呼叫网站。

    在链接代码期间已经链接了。在运行时,引导方法运行并在实际代码中进行串联链接。它使用适当的invokedynamic调用重写invokestatic调用。 这将从常量池中加载常量字符串,利用bootstrap方法static args将这些和其他常量直接传递给bootstrap方法调用。

  • 使用invokedynamic指令: - 通过在初始调用期间提供一次引导调用目标的方法,它为延迟链接提供了便利。 此处优化的具体想法是使用对StringBuilder.append的简单invokedynamic调用替换整个java.lang.invoke.StringConcatFactory舞蹈,这将接受需要连接的值。

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

答案 2 :(得分:18)

我在这里稍微添加一些细节。要获得的主要部分是字符串连接的完成方式是运行时决策,而不是编译时间。因此它可以改变,这意味着您已经编译了代码一次针对java-9 并且它可以更改底层实现,但它需要,而无需重新编译。

第二点是目前有6 possible strategies for concatenation of String

 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
}

您可以通过参数-Djava.lang.invoke.stringConcat选择其中任何一个。请注意,StringBuilder仍然是一个选项。