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

Posted

技术标签:

【中文标题】Java 中 toString() 中的 StringBuilder 与字符串连接【英文标题】:StringBuilder vs String concatenation in toString() in Java 【发布时间】:2010-12-04 16:25:59 【问题描述】:

鉴于以下 2 个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 个属性,它可能不会有什么不同,但你会在什么时候从 + concat 切换到 StringBuilder

【问题讨论】:

什么时候切换到 StringBuilder?当它影响内存或性能时。或者什么时候可能。如果你真的只为几个字符串做一次,不用担心。但是,如果您要一遍又一遍地这样做,那么在使用 StringBuilder 时应该会看到明显的差异。 参数中100的平均值是多少? @UnKnown 100 是 StringBuilder 的初始大小 @nonsequitor 所以最大字符数是 100? @Unknown 不只是初始大小,如果您知道要处理的字符串的大致大小,那么您可以告诉StringBuilder 预先分配多少大小,否则它将在用完时空间,必须通过创建一个新的 char[] 数组来将大小翻倍,然后复制数据 - 这是昂贵的。你可以通过给出大小作弊,然后就不需要创建这个数组了——所以如果你认为你的字符串大约 100 个字符长,那么你可以将 StringBuilder 设置为那个大小,它永远不必在内部扩展。跨度> 【参考方案1】:

版本 1 更可取,因为它更短且 the compiler will in fact turn it into version 2 - 没有任何性能差异。

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

当您在循环中连接时 - 这通常是编译器无法自行替换 StringBuilder 的时候。

【讨论】:

确实如此,但语言参考也指出这是可选的。其实我只是用JRE 1.6.0_15做了一个简单的测试,在反编译的类中并没有看到任何编译器优化。 我刚刚尝试了问题中的代码(在 JDK 1.6.0_16 上编译)并发现了预期的优化。我很确定所有现代编译器都会这样做。 你是对的。查看字节码,我可以清楚地看到 StringBuilder 优化。我正在使用反编译器,并且以某种方式将其转换回 concat。 +1 不是打死马,但规范中的措辞是:To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression. 关键字是 may。鉴于这是官方可选的(尽管很可能已实施),我们不应该保护自己吗? @Lucas:不,我们不应该。如果编译器决定不执行该优化,那将是因为它不值得。在 99% 的情况下,编译器更清楚哪种优化是值得的,因此根据经验,开发人员不应干预。当然,您的情况可能属于另外 1%,但这只能通过(仔细)基准测试来检查。【参考方案2】:

关键是你是在一个地方写一个单一的连接还是随着时间的推移积累它。

对于您给出的示例,显式使用 StringBuilder 是没有意义的。 (查看第一个案例的编译代码。)

但是如果你正在构建一个字符串,例如在循环内,使用 StringBuilder。

为了澄清,假设 hugeArray 包含数千个字符串,代码如下:

...
String result = "";
for (String s : hugeArray) 
    result = result + s;

与以下相比非常浪费时间和内存:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) 
    sb.append(s);

String result = sb.toString();

【讨论】:

是的,StringBuilder 不需要一遍又一遍地重新创建 String 对象。 该死的我用这两个函数来测试我正在处理的一个大字符串。6.51min vs 11secs 顺便说一句,您也可以使用result += s;(在第一个示例中) 这条语句会创建多少个对象? "a:"+ a + ", b:" + b + ", c: " + c +""; 类似的东西怎么样: String str = (a == null) ?空:a' +(b ==空)?空:b' +(c ==空)? c : c' + ...; ?这会阻止优化的发生吗?【参考方案3】:

在大多数情况下,您不会看到这两种方法之间的实际差异,但很容易构建像这样的最坏情况:

public class Main

    public static void main(String[] args)
    
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    

    private static void fast()
    
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    

    private static void slow()
    
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    

输出是:

slow elapsed 11741 ms
fast elapsed 7 ms

问题在于,将 += 附加到字符串会重建一个新字符串,因此它的成本与字符串长度(两者之和)呈线性关系。

所以 - 你的问题:

第二种方法会更快,但它的可读性较差且更难维护。 正如我所说,在您的具体情况下,您可能看不到差异。

【讨论】:

不要忘记 .concat()。我推测经过的时间在 10 到 18 毫秒之间,因此在使用像原始帖子示例这样的短字符串时可以忽略不计。 虽然您对+= 的看法是正确的,但原始示例是+ 的序列,编译器将其转换为单个string.concat 调用。您的结果不适用。 @Blindy & Droo :- 你们都是对的。在这种情况下使用 .concate 是最好的解决方法,因为 += 每次循环例程执行时都会创建新对象。 你知道他的toString()不是循环调用的吗? 我试过这个例子来测试速度。所以我的结果是:慢了 29672 毫秒;快速过去了 15 毫秒。所以答案是显而易见的。但如果是 100 次迭代 - 时间是相同的 - 0 毫秒。如果 500 次迭代 - 16 毫秒和 0 毫秒。以此类推。【参考方案4】:

我更喜欢:

String.format( "a: %s, b: %s, c: %s", a, b, c );

...因为它简短易读。

我会优化它以提高速度,除非您在重复次数非常高的循环中使用它并且测量了性能差异。

我同意,如果您必须输出大量参数,这种形式可能会令人困惑(就像其中一位 cmets 所说)。在这种情况下,我会切换到更易读的形式(可能使用 apache-commons 的 ToStringBuilder - 取自 matt b 的答案)并再次忽略性能。

【讨论】:

它实际上更长,包含更多符号并且有变量和乱序的文本。 那么你会说它的可读性低于其他方法之一吗? 我更喜欢写这个,因为添加更多变量更容易,但我不确定它是否更具可读性——尤其是当参数数量变大时。当您需要在不同时间添加位时,它也不起作用。 似乎更难阅读(对我来说)。现在我必须在 ... 和参数之间来回扫描。 我更喜欢这种形式,因为如果其中一个参数是null 是安全的【参考方案5】:

从 Java 1.5 开始,用“+”和 StringBuilder.append() 进行简单的一行连接会生成完全相同的字节码。

所以为了代码的可读性,使用“+”。

2 个例外:

多线程环境:StringBuffer 循环中的连接:StringBuilder/StringBuffer

【讨论】:

【参考方案6】:

我还与我的老板就是否使用 append 或 + 发生了冲突。因为他们正在使用 Append(我仍然无法弄清楚,正如他们每次创建新对象时所说的那样)。 所以我想做一些研发。虽然我喜欢 Michael Borgwardt 的解释,但我只是想展示一个解释,如果将来有人真的需要知道。

/**
 *
 * @author Perilbrain
 */
public class Appc 
    public Appc() 
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    

    public void Sb() 
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    

上述类的反汇编结果为

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

从上面的两个代码可以看出Michael是对的。每种情况下只创建一个SB对象。

【讨论】:

【参考方案7】:

使用最新版本的 Java(1.8) 反汇编(javap -c) 显示了编译器引入的优化。 +sb.append() 将生成非常相似的代码。但是,如果我们在 for 循环中使用 +,则值得检查其行为。

在 for 循环中使用 + 添加字符串

Java:

public String myCatPlus(String[] vals) 
    String result = "";
    for (String val : vals) 
        result = result + val;
    
    return result;

字节码:(for循环摘录)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

使用 stringbuilder.append 添加字符串

Java:

public String myCatSb(String[] vals) 
    StringBuilder sb = new StringBuilder();
    for(String val : vals) 
        sb.append(val);
    
    return sb.toString();

ByteCdoe:(for循环摘录)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

虽然有一点明显的差异。在第一种情况下,使用+,为每个for循环迭代创建新的StringBuilder,并通过执行toString()调用(29到41)存储生成的结果。因此,在 for 循环中使用 + 运算符时,您将生成真正不需要的中间字符串。

【讨论】:

这是 Oracle JDK 还是 OpenJDK ?【参考方案8】:

取决于字符串的大小。

请看下面的例子:

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) 
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder


static void case1() 
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) 
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");


static void case2() 
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) 
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");


static void case3() 
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) 
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");



static void saveTime(List<Long> executionTimes, long startTime) 
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) 
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    

输出:

java字节码版本:8 java.version:1.8.0_144 [str1.concat(str2)] 10000 次连接的平均时间:平均 0.096 毫秒 10000 次连接的平均时间:平均 0.185 毫秒 10000 次连接的平均时间:平均 0.327 毫秒 10000 次连接的平均时间:平均 0.501 毫秒 10000 次连接的平均时间:平均 0.656 毫秒 在 17745 毫秒内创建了长度为 1950000 的字符串 [str1+=str2] 10000 次连接的平均时间:平均 0.21 毫秒 10000 次连接的平均时间:平均 0.652 毫秒 10000 次连接的平均时间:平均 1.129 毫秒 10000 次连接的平均时间:平均 1.727 毫秒 10000 次连接的平均时间:平均 2.302 毫秒 在 60279 毫秒中创建了长度为 1950000 的字符串 [str1.append(str2)] 10000 次连接的平均时间:0.002 毫秒 avg 10000 次连接的平均时间:0.002 毫秒 avg 10000 次连接的平均时间:0.002 毫秒 avg 10000 次连接的平均时间:0.002 毫秒 avg 10000 次连接的平均时间:0.002 毫秒 avg 在 100 毫秒

内创建长度为 1950000 的字符串

随着字符串长度的增加,+=.concat 的连接时间也会增加,后者效率更高,但仍然是非恒定的 这就是肯定需要StringBuilder 的地方。

P.S.:我不认为 When to use StringBuilder in Java 真的是这个的复制品。 这个问题谈论的是toString(),大多数情况下它不执行大字符串的连接。


2019 年更新

自从java8 次以来,情况发生了一些变化。现在看来(java13),+= 的拼接时间实际上和str.concat() 是一样的。然而 StringBuilder 连接时间仍然是恒定的。 (上面的原始帖子经过略微编辑以添加更详细的输出)

java字节码版本:13 java.version:13.0.1 [str1.concat(str2)] 10000 次连接的平均时间:平均 0.047 毫秒 10000 次连接的平均时间:0.1 ms avg 10000 次连接的平均时间:平均 0.17 毫秒 10000 次连接的平均时间:平均 0.255 毫秒 10000 次连接的平均时间:0.336 ms avg 在 9147 ms 内创建了长度为 1950000 的字符串 [str1+=str2] 10000 次连接的平均时间:0.037 毫秒 avg 10000 次连接的平均时间:0.097 毫秒 avg 10000 次连接的平均时间:0.249 毫秒 avg 10000 次连接的平均时间:平均 0.298 毫秒 10000 次连接的平均时间:平均 0.326 毫秒 在 10191 ms 内创建了长度为 1950000 的字符串 [str1.append(str2)] 10000 次连接的平均时间:0.001 毫秒 avg 10000 次连接的平均时间:0.001 毫秒 avg 10000 次连接的平均时间:0.001 毫秒 avg 10000 次连接的平均时间:0.001 毫秒 avg 10000 次连接的平均时间:0.001 毫秒 avg 在 43 ms

内创建长度为:1950000 的字符串

还值得注意的是,bytecode:8/java.version:13 组合与 bytecode:8/java.version:8 相比具有良好的性能优势

【讨论】:

这应该是公认的答案.. 它取决于决定选择 concat 或 StringBuilder 的 String Stream 的大小【参考方案9】:

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

这个想法是用对 java.lang.invoke.StringConcatFactory 的简单调用动态调用替换整个 StringBuilder 追加舞蹈,这将接受需要连接的值。

【讨论】:

【参考方案10】:

出于性能原因,不鼓励使用+=String 连接)。原因是:Java String 是不可变的,每次进行新的连接时,都会创建一个新的 String (新的指纹与旧的指纹不同 in the String pool )。创建新字符串会给 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施加压力)。

【讨论】:

【参考方案11】:

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 的样式。

【讨论】:

【参考方案12】:

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

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

    使用 String builder 只会创建一个对象[StringBuilder 是可变的],然后附加字符串。

【讨论】:

为什么这个答案被否决了? docs.oracle.com/javase/8/docs/api/java/util/stream/… - 可变减少【参考方案13】:

尽可能使 toString 方法具有可读性!

在我的书中唯一的例外是,如果您可以向我证明它消耗大量资源:)(是的,这意味着分析)

另请注意,Java 5 编译器生成的代码比 Java 早期版本中使用的手写“StringBuffer”方法更快。如果您使用“+”,则此功能和未来的增强功能均免费提供。

【讨论】:

【参考方案14】:

目前的编译器是否仍然需要使用 StringBuilder 似乎存在一些争议。所以我想我会给我2美分的经验。

我有一个包含 10k 条记录的 JDBC 结果集(是的,我需要所有这些记录在一个批次中。)在我的机器上使用 + 运算符大约需要 5 分钟 Java 1.8。对于同一个查询,使用stringBuilder.append("") 只需不到一秒的时间。

所以差别很大。在循环内StringBuilder 要快得多。

【讨论】:

我认为辩论是关于在循环之外使用它。我认为您需要在循环中使用它达成共识。【参考方案15】:

这是我在 Java8 中检查的内容

使用字符串连接

使用 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

【讨论】:

【参考方案16】:

我能否指出,如果您要遍历集合并使用 StringBuilder,您可能需要查看 Apache Commons Lang 和 StringUtils.join()(不同风格)?

无论性能如何,它都会让您不必在第 百万次 时间创建 StringBuilder 和 for 循环。

【讨论】:

【参考方案17】:

使用“+”的性能明智的字符串连接成本更高,因为它必须制作一个全新的字符串副本,因为字符串在 java 中是不可变的。如果连接非常频繁,例如:在循环内,这将发挥特殊作用。 以下是我的 IDEA 在我尝试做这样的事情时的建议:

一般规则:

在单个字符串赋值中,使用字符串连接很好。 如果您要循环构建大量字符数据,请使用 StringBuffer。 在字符串上使用 += 总是比使用 StringBuffer 效率低,因此它应该敲响警钟 - 但在某些情况下,与可读性问题相比,获得的优化可以忽略不计,因此请使用您的常识

这是一个围绕这个主题的nice Jon Skeet blog。

【讨论】:

你不应该使用 StringBuffer 除非你绝对需要从多个线程同步访问。否则更喜欢不同步的 StringBuilder,因此开销较小。【参考方案18】:

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

【讨论】:

【参考方案19】:

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

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

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

【讨论】:

使用 concat() 的性能可能比 '+' 差,因为 JLS 允许将 '+' 转换为 StringBuilder,并且很可能所有 JVM 都这样做或使用更有效的替代方法 - concat 不太可能在您的示例中创建并丢弃至少一个完整的中间字符串。

以上是关于Java 中 toString() 中的 StringBuilder 与字符串连接的主要内容,如果未能解决你的问题,请参考以下文章

java语言中的tostring方法是怎么回事,求详解。

在 Java 中,如何使用 Google 的 guava CharStreams.toString 进程中的 inputStream?

java中,Collection c = new ArrayList(); Collection 中的toString()方法

toString()方法

java中怎么用toString方法去除字符串中的标点,符号和数字?

Java中的 toString 方法