Java如何使用"+"进行字符串连接?

Tom*_*ito 10 java string

我读到了Java使用+=运算符的方式StringBuilder.
操作是否相同("a" + "b")

T.J*_*der 36

如果组合文字字符串(字面意思"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;
}
Run Code Online (Sandbox Code Playgroud)

......什么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;
}
Run Code Online (Sandbox Code Playgroud)

(是的,真的 - 请参阅本答案末尾的反汇编.)请注意,它StringBuilder在每次迭代时都创建了一个新的,然后将结果转换为String.由于所有的临时内存分配,这是低效的(但除非你做了很多事情并不重要):它分配一个StringBuilder及其缓冲区,很可能在第一个上重新分配缓冲区append[如果rv超过16个字符long,这是默认的缓冲区大小]如果不在第一个,那么几乎肯定在第二个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();
}
Run Code Online (Sandbox Code Playgroud)

在那里,我们使用了显式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;
    }
}
Run Code Online (Sandbox Code Playgroud)

......这样拆解(使用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

}
Run Code Online (Sandbox Code Playgroud)

请注意如何在循环的每次迭代中StringBuilder创建new 并使用默认缓冲区容量创建.

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

我的经验法则是当我看到性能问题时,我担心它,或者我知道我正在进行大量连接,并且很可能是性能问题,并且从可维护性的角度来看,代码不会受到太大影响如果我用一个StringBuilder代替.狂热的反早熟优化联盟可能会在第二种情况下与我不同意.:-)


Pab*_*ruz 14

不,这是没有使用相同的StringBuilder比做"a" + "b".

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

所以,如果你这样做:

String c = "a" + "b";
Run Code Online (Sandbox Code Playgroud)

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

另一方面,StringBuilder就像一个缓冲区,可以在追加新的字符串时增长.

StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".
Run Code Online (Sandbox Code Playgroud)

经验法则(由于我得到的评论而改变):

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

编译这种代码时,编译器优化也起着重要作用.

以下是对该主题进一步说明.

有关此问题的更多StackOVerflow问题:

在循环中重用StringBuilder会更好吗?

在Java中构建一系列分隔项的最佳方法是什么?

StringBuilder vs Java中toString()的字符串连接

  • 每次连接时都无法保证创建新的字符串; 它实际上取决于编译器.质量差的编译器会像你描述的那样表现...... (5认同)
  • 实际上,JLS需要编译时常量的连接产生一个实际的`String`.经验法则应该是,如果你要在一个循环中创建一个`String`,并且它有可能会循环很多次,那就使用`StringBuilder`.使用"StirngBuilder"使线性串联代码复杂化毫无意义. (5认同)
  • 在编译时,不仅将与+连接的字符串文字更改为单个字符串(在您的示例中,编译器会将其简化为String c ="ab";),编译器还可以使用+优化串联以使用StringBuilder .您应该只需要使用StringBuilder进行更复杂的追加,例如在循环中追加时. (2认同)

Don*_*ows 5

是的,它是相同的,但编译器可以在发出代码之前另外优化文字的连接,因此"a"+"b"可以"ab"直接发布.