Java中toString()方法中的StringBuilder与String拼接的区别

1111

下面有两个toString()实现,哪一个更好:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
或者
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个属性,可能没有什么区别,但你从何时开始切换使用 StringBuilder 而不是 + 进行字符串拼接呢?


62
何时应该使用 StringBuilder?当涉及到内存或性能问题时,或者可能会出现这些问题时。如果您只是偶尔处理几个字符串,那么没有太大问题。但如果您需要反复进行此类操作,则使用 StringBuilder 可以明显提高性能。 - ewall
1
参数中100的含义是什么? - Asif Mushtaq
3
@UnKnown 100 是 StringBuilder 的初始大小。 - non sequitor
1
@nonsequitor 那么最大字符数是100个? - Asif Mushtaq
12
不仅仅是初始大小,如果你知道处理的字符串大致大小,那么你可以告诉StringBuilder预先分配多少空间,否则当它用完空间时,会不得不通过创建新的char[]数组并复制数据来将大小加倍,这是代价高昂的。你可以通过指定大小来欺骗StringBuilder,这样就不需要创建新的数组——所以如果你认为你的字符串长度约为100个字符,那么你可以将StringBuilder设置为该大小,它就不必内部扩展了。 - non sequitor
显示剩余2条评论
20个回答

7

Apache Commons-Lang有一个ToStringBuilder类,非常易于使用。它能很好地处理附加逻辑以及格式化你想要的toString的外观。

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

将返回类似于com.blah.YourClass@abc1321f[a=whatever, b=foo]的输出。

或者使用链接形式更加简洁:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

如果您想使用反射来包含类的每个字段:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

你还可以自定义 ToString 的风格,如果需要的话。

6

我认为我们应该采用StringBuilder追加的方法。

原因如下:
  1. 字符串拼接每次都会创建一个新的字符串对象(因为String是不可变对象),所以它会创建3个对象。

  2. 使用StringBuilder只会创建一个对象(StringBuilder是可变的),并且进一步的字符串将被追加到其中。


1
为什么这个答案被踩了?https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference -可变规约 - user1428716

4

尽可能使toString方法易读!

唯一的例外是,如果你可以向我证明它会消耗大量资源 :)(是的,这意味着性能分析)

还要注意,Java 5编译器生成的代码比早期版本的Java中使用的手写“StringBuffer”方法更快。如果你使用“+”,这个和未来的增强都是免费的。


3

目前关于是否仍需要使用StringBuilder存在一些争议。因此,我想分享我的经验。

我有一个包含1万条记录的JDBC结果集(是的,我需要在一个批处理中使用所有记录)。在我的Java 1.8机器上,使用+运算符大约需要5分钟。对于相同的查询,使用stringBuilder.append("")仅需不到一秒钟。

因此,差距非常巨大。在循环内部,StringBuilder要快得多。


3
我认为这个讨论是关于在循环外部使用它的问题。我认为有一个共识,你需要在循环内部使用它。 - Jordan

3

我认为这张图片非常有用,可以比较所有与String相关的类:

在此输入图片描述


1
你能告诉我你使用的Java版本是哪个吗? - wayne
3
@wayne他从这篇博客中复制粘贴了该图片。从它的pom.xml文件来看,它是Java 7。 - Shayan Ahmad
@wayne 我认为这可能不会有太大的区别。性能比率在所有版本中可能都是相同的。我不知道版本,因为我是在博客上找到的。 - Samir Alakbarov
@user16320675 这个统计数据属于Java 7。 - Samir Alakbarov

3

以下是我在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

2
值得一提的是,正如@ZhekaKozlov所指出的那样,

+自Java 9以来速度更快,除非JVM不知道如何优化它(例如在循环中进行连接)。

我检查了以下代码的字节码(在Java 17中):
public class StringBM {
    public String toStringPlus(String a) {
        return "{a:" + a + ", b:" + ", c: " + "}";
    }

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

关于 toStringPlus
 0: aload_1
 1: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
 6: areturn

关于 toStringBuilder:
 0: new           #11                 // class java/lang/StringBuilder
 3: dup
 4: bipush        100
 6: invokespecial #13                 // Method java/lang/StringBuilder."<init>":(I)V
 9: astore_2
10: aload_2
11: ldc           #16                 // String {a:
13: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: aload_1
17: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc           #22                 // String , b:
22: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: ldc           #24                 // String , c:
27: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: ldc           #26                 // String }
32: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #28                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: areturn

“+” 版本只需调用动态函数 makeConcatWithConstants 并传入方法参数 {a:\u0001, b:, c: }(其中 \u0001 是参数占位符)。而 StringBuilder 版本必须以“诚实”的方式完成。我想我们现在可以看出为什么“+”更快了。”

1

我可以指出,如果你要遍历一个集合并使用StringBuilder,你可能想要查看Apache Commons LangStringUtils.join()(不同的风味)?

无论性能如何,这将节省您创建StringBuilders和for循环的时间,这似乎是第百万次了。


1

在性能方面,使用“+”进行字符串连接的代价更高,因为它必须创建一个全新的字符串副本,因为在Java中字符串是不可变的。如果连接非常频繁,例如:在循环内部,这会产生特定的影响。 以下是当我尝试执行此操作时,我的IDEA建议的内容:

enter image description here

总则:

  • 在单个字符串赋值中,使用字符串连接是可以的。
  • 如果您正在循环构建大量字符数据,请使用StringBuffer。
  • 对于一个字符串使用+=总是比使用StringBuffer效率低,因此应该引起警惕 - 但在某些情况下,所获得的优化与可读性问题相比微不足道,因此要运用常识。

这里有一篇Jon Skeet关于这个主题的好博客


3
除非您确实需要多个线程之间同步访问,否则不应使用StringBuffer。否则,请优先考虑StringBuilder,它不是同步的,因此开销更小。 - Erki der Loony

-4

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

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

按顺序,我会说构建字符串的首选方法是使用StringBuilder、String#concat(),然后是重载的+运算符。当处理大型字符串时,使用StringBuilder可以显著提高性能,就像使用+运算符会导致性能大幅下降(随着字符串大小的增加呈指数级下降)一样。使用.concat()的一个问题是它可能会抛出NullPointerException异常。

11
在您的这个例子中,使用concat()方法的性能可能比使用"+"操作符差,因为JLS允许将"+"操作符转换为StringBuilder,大多数JVM都会这样做或使用更高效的替代方法。然而,使用concat()方法则需要创建并且至少丢弃一个完整的中间字符串,这种情况可能不适用于所有情况。 - Lawrence Dol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接