【问题标题】:How does the Java compiler handle StringBuilder(a + b + c + d)?Java 编译器如何处理 StringBuilder(a + b + c + d)?
【发布时间】:2021-04-09 10:00:35
【问题描述】:

我正在处理一个包含大量遗留代码的应用程序。 我经常看到的是在 StringBuilder 参数中使用“+”进行字符串连接。
示例:

StringBuilder sb = new StringBuilder("This "
        + "looks "
        + "rather "
        + "weird "
        + "to "
        + "me.") 

据我所知,编译器将使用 + 运算符的字符串连接替换为 StringBuilder().append()。
恐怕现在编译器会创建一个临时 StringBuilder 来执行连接,然后转换为 toString() 并将结果插入现有的 StringBuilder。

我的问题是:编译器是否能够优化嵌套的 StringBuilder ?如果不是,我应该重写代码以节省几个 CPU 周期吗?它显然有效,但每次看到它都会伤害我的眼睛。

感谢您的任何见解!

【问题讨论】:

  • 你在哪里看到compiler replaces a string concatenation that uses the + operator with StringBuilder().append()?如果这是真的,我们不会关心使用 StringBuilder 或字符串连接
  • @azro:这个逻辑根本不符合。 StringBuilder 允许您动态地 追加(例如在循环中)。我相信+确实使用StringBuilder
  • @azro 是的,通过+ 使用连接隐式转换为使用StringBuilder。只有在连接多个语句时,尤其是循环时,才应该显式使用StringBuilder
  • @azro: 在调用栈中可以看到

标签: java stringbuilder


【解决方案1】:

编译器优化字符串文字常量的连接:

StringBuilder sb = new StringBuilder("This "
        + "looks "
        + "rather "
        + "weird "
        + "to "
        + "me.");

在运行时完全等同于:

StringBuilder sb = new StringBuilder("This looks rather weird to me.");

这也适用于任何 String 编译时常量,而不仅仅是文字。

您可能已经阅读过有关使用非常数时的担忧:

StringBuilder sb = new StringBuilder("This "
        + arg1
        + var2);

相当于:

StringBuilder sb = new StringBuilder(new StringBuilder("This ")
        .append(arg1)
        .append(var2)
        .toString());

如果您将其重写为:

StringBuilder sb = new StringBuilder("This ")
        .append(arg1)
        .append(var2);

【讨论】:

  • 如果字符串生成器不用于其他任何用途,将其替换为"This " + arg1 + var2 效率更高。处理StringBuilder 仅在涉及条件片段或循环时才会得到回报。顺便说一句,编译时常量折叠适用于所有常量,而不仅仅是字符串,例如"a " + 1 + " b" 编译为 "a 1 b"
【解决方案2】:

要添加到@k314159 的答案,确定编译器如何转换任何特定代码块很容易,如果你隔离它,用javac 编译它,然后使用javap 在其上运行反汇编程序以查看生成的(注释) 字节码。

例如,如果您将 StringBuilder 代码隔离到一个最小类中:

public class Test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("This "
        + "looks "
        + "rather "
        + "weird "
        + "to "
        + "me.");
    }
}

然后您可以像这样编译和反汇编输出:

javac Test.java && javap -c Test.class

输出是:

Compiled from "Test.java"
public class Test {
  public Test();
    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: ldc           #3                  // String This looks rather weird to me.
       6: invokespecial #4                  // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       9: astore_1
      10: return
}

在这种情况下,您感兴趣的线路应该在这里:

4: ldc           #3                  // String This looks rather weird to me.

您可能已经猜到了,这条指令将一个(字符串)常量压入堆栈。注释清楚地表明本例中的常量是单个字符串,即:"This looks rather weird to me."

希望能稍微揭开内部的神秘面纱!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 1970-01-01
    • 1970-01-01
    • 2018-09-14
    • 2011-06-13
    相关资源
    最近更新 更多