给定下面的2个toString()实现,哪个是首选的:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

or

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

更重要的是,鉴于我们只有3个属性,它可能不会有什么不同,但在什么时候你会从+ concat切换到StringBuilder?


当前回答

对于像这样的简单字符串,我更喜欢使用

"string".concat("string").concat("string");

按照顺序,我认为构造字符串的首选方法是使用StringBuilder, string# concat(),然后是重载的+运算符。在处理大字符串时,StringBuilder显著提高了性能,就像使用+操作符会大幅降低性能一样(随着字符串大小的增加,性能会呈指数级下降)。使用.concat()的一个问题是它会抛出nullpointerexception。

其他回答

我认为这张图对于比较所有使用string的类非常有用:

出于性能考虑,不鼓励使用+=(字符串连接)。原因是:Java String是一个不可变的,每当一个新的连接完成时,一个新的String就会被创建(新的String与已经在String池中的旧String具有不同的指纹)。创建新的字符串会给GC带来压力,并降低程序的运行速度:创建对象的开销很大。

下面的代码将使其更加实用和清晰。

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

下面报告了运行的结果。

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

不考虑1个连接的结果(JIT还没有完成它的工作),即使对于10个连接,性能惩罚也是相关的;对于成千上万的连接,差异是巨大的。

从这个非常快速的实验中得到的教训(很容易用上面的代码重现):永远不要使用+=将字符串连接在一起,即使是在需要一些连接的非常基本的情况下(正如前面所说,创建新字符串无论如何都是昂贵的,并且会给GC带来压力)。

我认为我们应该使用StringBuilder附加方法。 原因是:

String连接每次都会创建一个新的String对象(因为String是不可变对象),所以它将创建3个对象。 使用String生成器只会创建一个对象[StringBuilder is mutable],并且进一步的字符串被追加到它。

这是我在Java8中检查的

Using String concatenation Using StringBuilder long time1 = System.currentTimeMillis(); usingStringConcatenation(100000); System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms"); time1 = System.currentTimeMillis(); usingStringBuilder(100000); System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms"); private static void usingStringBuilder(int n) { StringBuilder str = new StringBuilder(); for(int i=0;i<n;i++) str.append("myBigString"); } private static void usingStringConcatenation(int n) { String str = ""; for(int i=0;i<n;i++) str+="myBigString"; }

如果对大量的字符串使用字符串连接,这真的是一场噩梦。

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

在Java 9中,版本1应该更快,因为它被转换为invokedynamic调用。更多细节可以在JEP-280中找到:

其思想是用对java.lang.invoke的简单invokedynamic调用替换整个StringBuilder追加舞蹈。StringConcatFactory,它将接受需要连接的值。