代码之家  ›  专栏  ›  技术社区  ›  Student

Java如何使用“+”进行字符串串接?

  •  8
  • Student  · 技术社区  · 16 年前

    我读到Java的工作方式 += 操作员,使用 StringBuilder .
    和A一样吗 ("a" + "b") 操作?

    6 回复  |  直到 8 年前
        1
  •  14
  •   Ravi    8 年前

    不,用起来不一样 StringBuilder 比做 "a" + "b" .

    在爪哇中,字符串实例是不可变的。

    所以,如果你这样做:

    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".
    

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

    如果要连接很多(例如,在循环中连接,或生成由多个字符串连接的变量组成的大型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

        2
  •  36
  •   T.J. Crowder    15 年前

    如果你结合 字面意义的 字符串(字面上 "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;
    }
    

    (是的,真的-请参阅本答案末尾的反汇编。)请注意,它创建了一个新的 字符串拼接 在每次迭代中,然后将结果转换为 String . 这是低效的(但这并不重要,除非你做的是 许多 )因为所有的临时内存分配:它分配 字符串拼接 它的缓冲区,很可能在第一个 append [如果 rv 长度超过16个字符,这是默认缓冲区大小],如果不是第一个字符,则几乎肯定是第二个字符。 追加 ,然后分配 弦 最后-然后再做 再来一次 在下一个迭代中。

    如果需要,您可以通过重写它来显式地使用 字符串拼接 :

    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();
    }
    

    在那里我们用了一个明确的 字符串拼接 并将其初始缓冲容量设置为足以容纳结果的大小。这会提高内存效率,但当然,对于缺乏经验的代码维护人员来说,这一点不太清楚,而且编写起来也有点麻烦。所以 如果 您发现紧字符串concat循环存在性能问题,这可能是解决它的一种方法。

    你可以在封面下看到这个 字符串拼接 使用以下测试类:

    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
    
    }
    

    注意新的 字符串拼接 创建于 每个 循环的迭代,并使用默认缓冲区容量创建。

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

    我的经验法则是当我看到一个性能问题时,或者如果我知道我正在做一个 许多 是串联的,它是 很有可能 如果我使用 字符串拼接 相反。狂犬病反过早优化联盟可能会在第二个问题上与我意见不同。-)

        3
  •  5
  •   Donal Fellows    16 年前

    是的,它是相同的,但是编译器可以另外优化 字面量 在发布代码之前,所以 "a"+"b" 可作为 "ab" 直接。

        4
  •  3
  •   Christian Semrau Louis Wasserman    16 年前

    用于连接固定数量的字符串 在一个表达式中 具有 + 编译器将使用一个 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" 相反。

    正如网上所提到的,你不应该使用 + 建立一个字符串 在循环中 ,因为您正在将字符串的开头反复复制到新字符串。在这种情况下,你应该使用一个 字符串拼接 在循环之外声明。

        5
  •  0
  •   ring bearer    16 年前

    "a" + "b" 操作

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

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

    使用 StringBuilder

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

    还要注意的是,在Sun Java编译器中自动创建优化。 StringBuilders ( StringBuffers <5.0)当它看到字符串连接时。但这只是Sun Java编译器。

        6
  •  -2
  •   brettkelly    16 年前

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

    Source