【问题标题】:How Java do the string concatenation using "+"?Java如何使用“+”进行字符串连接?
【发布时间】:2023-03-02 23:59:01
【问题描述】:

我读到了 Java 使用 += 运算符的方式,使用 StringBuilder
("a" + "b")操作一样吗?

【问题讨论】:

标签: java string


【解决方案1】:

如果你结合 literal 字符串(字面意思是 "foo" + "bar"),编译器会在编译时而不是在运行时进行。

如果您有两个非文字字符串并将它们与+ 连接起来,编译器(无论如何是Sun 的)将在幕后使用StringBuilder,但不一定以最有效的方式。例如,如果你有这个:

String repeat(String a, int count) {
    String rv;

    if (count <= 0) {
        return "";
    }

    rv = a;
    while (--count > 0) {
        rv += a;
    }
    return rv;
}

...Sun 编译器实际生成的字节码看起来是这样的某些东西

String repeat(String a, int count) {
    String rv;

    if (count <= 0) {
        return "";
    }

    rv = a;
    while (--count > 0) {
        rv = new StringBuilder().append(rv).append(a).toString();
    }
    return rv;
}

(是的,真的 - 请参阅此答案末尾的反汇编。)请注意,它在每次迭代时创建一个新的 StringBuilder,然后将结果转换为 String。由于所有的临时内存分配,这是低效的(但除非你经常这样做,否则没关系):它分配一个 StringBuilder 及其缓冲区,很可能重新分配缓冲区在第一个append [如果rv 的长度超过16 个字符,这是默认缓冲区大小],如果不是在第一个,那么几乎可以肯定在第二个append,然后在最后分配一个String — 然后在下一次迭代中再次

如有必要,您可以通过重写以显式使用StringBuilder 来提高效率:

String repeat(String a, int count) {
    StringBuilder rv;

    if (count <= 0) {
        return "";
    }

    rv = new StringBuilder(a.length() * count);
    while (count-- > 0) {
        rv.append(a);
    }
    return rv.toString();
}

我们使用了显式的StringBuilder 并将其初始缓冲区容量设置为足够大以容纳结果。这更节省内存,但当然,对于没有经验的代码维护人员来说,这有点不太清楚,而且写起来也有点痛苦。所以如果你发现一个紧密的字符串连接循环的性能问题,这可能是解决它的一种方法。

您可以在下面的测试类中看到这个 StringBuilder 的幕后操作:

public class SBTest
{
    public static final void main(String[] params)
    {
        System.out.println(new SBTest().repeat("testing ", 4));
        System.exit(0);
    }

    String repeat(String a, int count) {
        String rv;

        if (count <= 0) {
            return "";
        }

        rv = a;
        while (--count > 0) {
            rv += a;
        }
        return rv;
    }
}

...这样反汇编(使用javap -c SBTest):

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

public static final void main(java.lang.String[]);
Code:
   0: getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: new   #3; //class SBTest
   6: dup
   7: invokespecial  #4; //Method "<init>":()V
   10: ldc   #5; //String testing
   12: iconst_4
   13: invokevirtual  #6; //Method repeat:(Ljava/lang/String;I)Ljava/lang/String;
   16: invokevirtual  #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   19: iconst_0
   20: invokestatic   #8; //Method java/lang/System.exit:(I)V
   23: return

java.lang.String repeat(java.lang.String, int);
Code:
   0: iload_2
   1: ifgt  7
   4: ldc   #9; //String
   6: areturn
   7: aload_1
   8: astore_3
   9: iinc  2, -1
   12: iload_2
   13: ifle  38
   16: new   #10; //class java/lang/StringBuilder
   19: dup
   20: invokespecial  #11; //Method java/lang/StringBuilder."<init>":()V
   23: aload_3
   24: invokevirtual  #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   27: aload_1
   28: invokevirtual  #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   31: invokevirtual  #13; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   34: astore_3
   35: goto  9
   38: aload_3
   39: areturn

}

注意如何在循环的每次迭代中创建一个新的StringBuilder,并使用默认缓冲区容量创建。

所有这些临时分配的东西听起来都很丑陋,但同样,只有当您处理大量循环和/或大量字符串时。此外,当生成的字节码运行时,JVM 可能会进一步优化它。例如,Sun 的 HotSpot JVM 是一个非常成熟的 JIT 优化编译器。一旦将循环识别为热点,它很可能会找到重构它的方法。或者不是,当然。 :-)

我的经验法则是,当我看到性能问题时,我会担心它,或者如果我知道我正在执行 很多 连接并且它很可能是一个性能问题,如果我改用StringBuilder,从可维护性的角度来看,代码不会受到重大影响。狂热的反过早优化联盟可能会在其中的第二个问题上不同意我的看法。 :-)

【讨论】:

  • @Tom Brito:实际上,基于这个问题,我不确定一个词的答案是“是”还是“不是”,所以我把它拿出来并解释了发生了什么. :-)
  • @T.J.Crowder 你摇滚!!(我知道这不是推荐的评论......我无法抗拒:P)
【解决方案2】:

没有。使用StringBuilder 和使用"a" + "b" 不一样。

在 Java 中,String 实例是不可变的。

所以,如果你这样做:

String c = "a" + "b";

每次连接时都会创建新的字符串。

另一方面,StringBuilder 就像一个缓冲区,可以在追加新字符串时根据需要增长。

StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".

经验法则是(由于我得到了 cmets 而改变):

如果您要进行大量连接(即在循环内进行连接,或生成由多个字符串连接变量组成的大型 XML),请使用 StringBuilder。否则,简单的连接(使用 + 运算符)就可以了。

编译器优化在编译此类代码时也发挥着重要作用。

Here's对该主题的进一步解释。

还有关于这个问题的更多 StackOVerflow 问题:

Is it better to reuse a StringBuilder in a loop?

What's the best way to build a string of delimited items in Java?

StringBuilder vs String concatenation in toString() in Java

【讨论】:

  • 不能保证每次连接时都会创建新字符串;这实际上取决于编译器。质量差的编译器会像你描述的那样表现......
  • 不一定。无论如何,编译器都会对其进行优化。将您的经验法则更改为:“如果您要使用stringA += stringB,请改用StringBuilder,因为它确实会吃掉堆。您的回答更暗示您需要一个StringBuilder 为每个stringA + stringB ,这不是真的。
  • 事实上,JLS 要求编译时常量的串联导致一个实习生String。经验法则应该是,如果您要在循环中创建String,并且它有可能循环多次,请使用StringBuilder。用StirngBuilder 使线性串联代码复杂化毫无意义。
  • 不仅在编译时与 + 连接的字符串文字会更改为单个字符串(在您的示例中,编译器会将其简化为 String c = "ab";),编译器还可以优化与 + 连接以使用 StringBuilder 无论如何。您应该只需要使用 StringBuilder 进行更复杂的追加,例如在循环中追加时。
  • 同意。和大家一起。只是试图保持简单。我认为这些问题要求一般性的答案,包括“a”+“b”也可以是strA+strB的情况。
【解决方案3】:

是的,是一样的,但是编译器可以在发布代码之前额外优化literals的连接,所以"a"+"b"可以直接发布为"ab"

【讨论】:

  • 不,他们不一样;它们怎么能 - 与不可变字符串的连接和与字符串构建器的连接? @Pablo Santa Cruz 提供了一个有价值的答案。
  • @phoenix24:实际上,他准确地 吐出愚蠢的垃圾,因为他不区分字面连接和非字面连接。
【解决方案4】:

为了将固定数量的字符串在一个表达式中+连接起来,编译器将使用单个StringBuilder生成代码。

例如线

String d = a + b + c;

产生与行相同的字节码

String d = new StringBuilder().append(a).append(b).append(c).toString();

使用 javac 编译器编译时。 (Eclipse 编译器通过调用new StringBuilder(a) 生成更优化的代码,从而节省了一个方法调用。)

正如其他答案中提到的,编译器会将像 "a" + "b" 这样的字符串文字连接成一个字符串本身,生成包含 "ab" 的字节码。

正如网上到处提到的,你不应该使用+ 来构建一个字符串在一个循环中,因为你将字符串的开头一遍又一遍地复制到新的字符串中。在这种情况下,您应该使用一个在循环外声明的StringBuilder

【讨论】:

    【解决方案5】:

    "a" + "b"操作

    虽然可读、易于格式化和直接,但用“+”连接字符串在 Java 中被认为是不好的。

    每次通过 '+' (String.concat()) 追加内容时,都会创建一个新字符串,复制旧字符串内容,追加新内容,并丢弃旧字符串。字符串越大,花费的时间就越长——要复制的内容越多,产生的垃圾就越多。 注意:如果您只是连接几个(比如 3,4)字符串,而不是通过循环构建字符串或只是编写一些测试应用程序,您仍然可以坚持使用“+”

    使用StringBuilder

    在执行大量字符串操作(或通过循环追加)时,可能建议将“+”替换为StringBuilder.append。在append() 方法调用期间不会创建“+”中提到的中间对象。

    还要注意 Sun Java 编译器中的优化,当它看到字符串连接时会自动创建 StringBuilders (StringBuffers

    【讨论】:

      【解决方案6】:

      字符串更常见的是连接 使用 + 运算符,如 "Hello," + " world" + "!"

      Source

      【讨论】:

      • 是的,但我认为他的意思是,编译器如何执行该操作。 (答案确实是它使用了StringBuilder 隐藏起来。)
      猜你喜欢
      • 2020-07-01
      • 2012-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-23
      相关资源
      最近更新 更多