Javac StringBuilder/StringBuffer 优化是啥时候引入的?

Posted

技术标签:

【中文标题】Javac StringBuilder/StringBuffer 优化是啥时候引入的?【英文标题】:When was Javac StringBuilder/StringBuffer optimization introduced?Javac StringBuilder/StringBuffer 优化是什么时候引入的? 【发布时间】:2017-07-18 04:40:51 【问题描述】:

我知道 Javac 编译器能够使用 StringBuilder/StringBuffer 转换字符串连接 +,我很想知道 从哪个版本开始引入了此更改?

我正在使用这个示例代码:

public class Main 
  public static void main(String[] args) 
      String a = args[0];
      String s = "a";
      s = s + a;
      s = s + "b";
      s = s + "c";
      s = s + "d";
      s = s + "e";
      System.out.println(s);
  

到目前为止,我已经尝试过 javac 1.8.0_121javac 1.6.0_20javac 1.5.0_22java 1.4.2_19

这是我使用来自1.4.2_19javap -c 看到的字节码示例:

6:  astore_2
7:  new #3; //class StringBuffer
10: dup
11: invokespecial   #4; //Method java/lang/StringBuffer."<init>":()V
14: aload_2
15: invokevirtual   #5; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
18: aload_1
19: invokevirtual   #5; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
22: invokevirtual   #6; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;

所有 4 个版本似乎都在使用 StringBuilder/StringBuffer 优化,所以我很想知道从哪个 Javac 版本开始引入了此更改?

【问题讨论】:

大概从一开始就... Java 5 如果我没记错的话 更有用的是知道他们中的任何一个是否可以对使用循环构建的字符串执行此操作。 请注意,编译器并不总是自动优化它。去年,我使用String#+ 而不是StringBuilder,让一台带有Java 8 的Linux 服务器瘫痪了。我想构建一个相当大(大约 300MB)的 gnuplot 文件。这涉及到一些逻辑,我必须并行构建两个 150MB 的字符串,并在最后将它们连接起来。使用String#+,花了半个小时,所有可用内存。使用StringBuilder,只需几秒钟,而且内存更少。 【参考方案1】:

这是language specification from version 1的引述:

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

当时,他们使用的是StringBuffer,而不是StringBuilder

同样引用JDK1.0.2的StringBuffer

这个类是一个可增长的字符缓冲区。它主要用于创建字符串。编译器使用它来实现“+”运算符。

【讨论】:

不错的发现,但现在我不得不鄙视我以前的老师,因为他们教我使用 StringBuffer/Builder 的无用冗长语法;-( 我会尝试找到一些安慰,相信这一点它可能在 OpenJDK 的早期版本中没有优化,请不要破坏它:p @Aaron StringBuilder 通常应该在构造复杂字符串时使用,例如在循环中(参见***.com/questions/1532461/…)。 哦,感谢您的精确!虽然这不是问题的重点,但我认为添加您的答案可能仍然很有趣,因为 OP 和其他读者可能会忽略这种优化并不总是可能的,并且可能会得出结论,因为我错误地认为在任何地方都使用字符串连接是很好。 @Aaron 这不是不必要的冗长。直接使用StringBuilder 可以让您控制初始缓冲区大小。估计它可以为您节省一些昂贵的底层 char 数组的大小调整。 @Aaron 我不会称之为过早优化。例如,如果一个循环确实有 10000 个连接,那么将 + 用作 StringBuilder 并使用 16 个字符的默认缓冲区同样愚蠢。 + 实际上更快的一种情况是连接静态字符串值时。在这种情况下,可以在编译时构建生成的字符串。【参考方案2】:

我查阅了 Java 语言规范,第一版(从 1996 年开始)。不容易找到,但here it is。连接优化的那篇文章就在那时:

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

当时该规范适用于 StringBuffer,但 StringBuilder(当前 JLS 措辞所指)可能被认为性能更好,因为它的方法不同步。

然而,这并不意味着人们应该依赖优化,因为 总是 在适当的位置。例如,循环中的字符串连接不会得到优化。

【讨论】:

【参考方案3】:

JLS 已经在一些答案中给出。我只想指出 StringBuffer (https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html) 从 1.0 开始就存在,而

StringBuilder(https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) 出现在 1.5 版中。请参阅相应 javadocs 的 since: 部分。

【讨论】:

无需深入研究 JLS 或字节码...只需检查 Javadoc 中的 StringBuilder!它说Since: 1.5。所以这个答案是最好的答案。 (+1)【参考方案4】:

这不能回答问题,但我只想补充一点,在 jdk-9 中,StringBuilder::append允许的策略之一,但不是默认的。

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

它实际上是一个用于字符串连接的invokedynamic 字节码,因此它的实现现在是特定于 JRE 的,而不是编译器。顺便说一句,默认策略是:MH_INLINE_SIZED_EXACT

【讨论】:

抱歉,您能解释一下允许的策略是什么意思吗?是专门针对字符串连接的一些运行时优化吗? @glee8e 我想我已经在这里讨论了一些主题:***.com/questions/40267601/…

以上是关于Javac StringBuilder/StringBuffer 优化是啥时候引入的?的主要内容,如果未能解决你的问题,请参考以下文章

CMD里面javac运行不了。。

javac如何编译.java,.jar

javac实现原理

初探Javac指令,你不知道的编译原理

Java面试题—Javac编译器

为啥我的java可以运行,javac不能运行