ChatGPT解决这个技术问题 Extra ChatGPT

StringBuilder vs String concatenation in toString() in Java

Given the 2 toString() implementations below, which one is preferred:

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

?

More importantly, given we have only 3 properties it might not make a difference, but at what point would you switch from + concat to StringBuilder?

At what point do you switch to StringBuilder? When it effects memory or performance. Or when it might. If you're really only doing this for a couple strings once, no worries. But if you're going to be doing it over and over again, you should see a measurable difference when using StringBuilder.
what is the mean of 100 in parameter?
@UnKnown 100 is the initial size of StringBuilder
@nonsequitor So the maximum characters will be 100?
@Unknown no just the initial size, if you know the approximate size of the string you are dealing with then you can tell StringBuilder how much size to allocate upfront otherwise it will, if it runs out of space, have to double the size by creating a new char[] array then copy data over - which is costly. You can cheat by giving the size and then there is no need for this array creation - so if you think your string will be ~100 chars long then you can set the StringBuilder to that size and it will never have to expand internally.

L
Liam

Version 1 is preferable because it is shorter and the compiler will in fact turn it into version 2 - no performance difference whatsoever.

More importantly given we have only 3 properties it might not make a difference, but at what point do you switch from concat to builder?

At the point where you're concatenating in a loop - that's usually when the compiler can't substitute StringBuilder by itself.


Not to beat a dead horse, but the wording in the spec is: 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. The key word there being may. Given that this is officially optional (though most likely implemented) should we not protect ourselves?
@Lucas: No, we should not. If the compiler decides not to perform that optimization, it will be because it is not worth it. In 99% of cases, the compiler knows better which optimization is worth it, so as a rule of thumb the dev should not interfere. Of course, your situation may fall into the other 1%, but that can only be checked by (careful) benchmarking.
@sleske, I do not think you are right. The compiler has limited ability to find possible optimisations. It won't do the thinking for you.
@Vach: the spec actually says "may use the StringBuffer class or a similar technique". Mentioning StringBuffer is a bit of an anachronism (especially since it's still unchanged in the current JLS for Java 8) but that's all. Besides, a modern JVM can often eliminate locks from synchronize code if it can determine that an object can never be accessed by different threads.
I don't think this is always true. I recently optimized a piece of code in a loop by removing all the "+"s and replacing them with a stringbuilder, and the performance went from ~500 iterations in 20 seconds to 35,000 iterations in 5 seconds. I was absolutely shocked by the difference. There were also some concatenations of integers that I replaced with String.format calls, which probably added to the improved performance; truthfully, I'm not sure which change had the biggest impact. But assuming that "+" is always just fine is a mistake.
j
joel.neely

The key is whether you are writing a single concatenation all in one place or accumulating it over time.

For the example you gave, there's no point in explicitly using StringBuilder. (Look at the compiled code for your first case.)

But if you are building a string e.g. inside a loop, use StringBuilder.

To clarify, assuming that hugeArray contains thousands of strings, code like this:

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

is very time- and memory-wasteful compared with:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

Yes, StringBuilder doesn't need to re-create the String object over and over.
Dammit I used those 2 function to test a big string I'm working in. 6.51min vs 11secs
By the way you can use result += s; as well (in the first example)
how many object will be created by this statement? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
What about something like: String str = (a == null) ? null : a' + (b == null) ? null : b' + (c == null) ? c : c' + ...; ? Will that prevent the optimization from happening?
P
Peter Mortensen

In most cases, you won't see an actual difference between the two approaches, but it's easy to construct a worst case scenario like this one:

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+="*";
    }
}

The output is:

slow elapsed 11741 ms
fast elapsed 7 ms

The problem is that to += append to a string reconstructs a new string, so it costs something linear to the length of your strings (sum of both).

So - to your question:

The second approach would be faster, but it's less readable and harder to maintain. As I said, in your specific case you would probably not see the difference.


Don't forget about .concat(). I would surmise the elapsed time to be anywhere from 10 to 18 ms making it negligible when using short strings like the original post example.
While you're right about +=, the original example was a sequence of +, that the compiler transforms to a single string.concat call. Your results don't apply.
@Blindy & Droo :- You both are right.Using .concate in such scenario is best workaround,as += creates new object every time loop routine executes.
do you know that his toString() is not called in a loop?
I've tried this example to test the speed. So my results are: slow elapsed 29672 ms; fast elapsed 15 ms. So the answer is obvious. But if it would be 100 iterations - time is the same - 0 ms. If 500 iterations - 16 ms and 0 ms. And so on.
d
dimo414

I prefer:

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

...because it's short and readable.

I would not optimize this for speed unless you use it inside a loop with a very high repeat count and have measured the performance difference.

I agree, that if you have to output a lot of parameters, this form can get confusing (like one of the comments say). In this case I'd switch to a more readable form (perhaps using ToStringBuilder of apache-commons - taken from the answer of matt b) and ignore performance again.


It's actually longer, contains more symbols and has variables a text out of sequence.
So would you say it's less readable than one of the other aproaches?
I prefer writing this, because it's easier to add more variables, but I'm not sure it's more readable -- especially as the number of arguments gets large. It also doesn't work for times when you need to add bits at different times.
Seems harder to read (for me). Now I have to scan back and forth between {...} and the parameters.
I prefer this form to, because it is safe if one of the parameter is null
Z
Zofren

Since Java 1.5, simple one line concatenation with "+" and StringBuilder.append() generate exactly the same bytecode.

So for the sake of code readability, use "+".

2 exceptions :

multithreaded environment : StringBuffer

concatenation in loops : StringBuilder/StringBuffer


And before Java 1.5, simple one line concatenation with "+" and StringBuffer.append() generated exactly the same bytecode (as StringBuilder did not exist). Since Java 9, simple one line concatenation with "+" produces code potentially better than StringBuilder.
N
Nicofisi

I also had clash with my boss on the fact whether to use append or +.As they are using Append(I still cant figure out as they say every time a new object is created). So I thought to do some R&D.Although I love Michael Borgwardt explaination but just wanted to show an explanation if somebody will really need to know in future.

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

and disassembly of above class comes out as

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

From the above two codes you can see Michael is right.In each case only one SB object is created.


r
ring bearer

Using latest version of Java(1.8) the disassembly(javap -c) shows the optimization introduced by compiler. + as well sb.append() will generate very similar code. However, it will be worthwhile inspecting the behaviour if we are using + in a for loop.

Adding strings using + in a for loop

Java:

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

ByteCode:(for loop excerpt)

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

Adding strings using stringbuilder.append

Java:

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

ByteCdoe:(for loop excerpt)

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

There is a bit of glaring difference though. In first case, where + was used, new StringBuilder is created for each for loop iteration and generated result is stored by doing a toString() call(29 through 41). So you are generating intermediate Strings that your really do not need while using + operator in for loop.


Is this Oracle JDK or OpenJDK ?
@ChristopheRoussy doesn’t matter as they consist of exactly the same code.
@Holger, according to Heinz Kabutz: "OpenJDK is 99% the same code as Oracle JDK (depending on what provider you're getting it from) so what this really boils down to is support". Not sure where that 1% resides or if that is still true.
@ChristopheRoussy the license headers, perhaps. I doubt that the “99%” is an exact measured number. It’s more a number saying “don’t come back at me nitpicking, if you found an irrelevant bit of difference”
M
Marinos An

It depends on the size of string.

See the example below:

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

Output:

java bytecode version:8 java.version: 1.8.0_144 [str1.concat(str2)] average time for 10000 concatenations: 0.096 ms avg average time for 10000 concatenations: 0.185 ms avg average time for 10000 concatenations: 0.327 ms avg average time for 10000 concatenations: 0.501 ms avg average time for 10000 concatenations: 0.656 ms avg Created string of length:1950000 in 17745 ms [str1+=str2] average time for 10000 concatenations: 0.21 ms avg average time for 10000 concatenations: 0.652 ms avg average time for 10000 concatenations: 1.129 ms avg average time for 10000 concatenations: 1.727 ms avg average time for 10000 concatenations: 2.302 ms avg Created string of length:1950000 in 60279 ms [str1.append(str2)] average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg Created string of length:1950000 in 100 ms

As the string length increases, so does the += and .concat concatenation times, with the latter being more efficient but still non-constant
That is where the StringBuilder is definitely needed.

P.S.: I don't think When to use StringBuilder in Java is really a duplicate of this.
This question talks about toString() which most of the times does not perform concatenations of huge strings.

2019 Update

Since java8 times, things have changed a bit. It seems that now(java13), the concatenation time of += is practically the same as str.concat(). However StringBuilder concatenation time is still constant. (Original post above was slightly edited to add more verbose output)

java bytecode version:13 java.version: 13.0.1 [str1.concat(str2)] average time for 10000 concatenations: 0.047 ms avg average time for 10000 concatenations: 0.1 ms avg average time for 10000 concatenations: 0.17 ms avg average time for 10000 concatenations: 0.255 ms avg average time for 10000 concatenations: 0.336 ms avg Created string of length:1950000 in 9147 ms [str1+=str2] average time for 10000 concatenations: 0.037 ms avg average time for 10000 concatenations: 0.097 ms avg average time for 10000 concatenations: 0.249 ms avg average time for 10000 concatenations: 0.298 ms avg average time for 10000 concatenations: 0.326 ms avg Created string of length:1950000 in 10191 ms [str1.append(str2)] average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg Created string of length:1950000 in 43 ms

Worth noting also bytecode:8/java.version:13 combination has a good performance benefit compared to bytecode:8/java.version:8


This should be the accepted answer .. it depends on the size of String Stream that determines the choice of concat or StringBuilder
actually this adresses a different case. OP was not concatenating in a loop. In case of inline concatenation, StringBuilder is actually less efficient, see the answer by @wayne
@klarki The fact that it is used inside toString() does not eliminate the possibility of having a huge string to concatenate (even once). The loop in my example just shows that, the bigger the string you want to concatenate in, the longer the time it takes with .concat().
Z
ZhekaKozlov

In Java 9 the version 1 should be faster because it is converted to invokedynamic call. More details can be found in JEP-280:

The idea is to replace the entire StringBuilder append dance with a simple invokedynamic call to java.lang.invoke.StringConcatFactory, that will accept the values in the need of concatenation.


P
Paolo Maresca

For performance reasons, the use of += (String concatenation) is discouraged. The reason why is: Java String is an immutable, every time a new concatenation is done a new String is created (the new one has a different fingerprint from the older one already in the String pool ). Creating new strings puts pressure on the GC and slows down the program: object creation is expensive.

Below code should make it more practical and clear at the same time.

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

Results for a run are reported below.

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

Not considering the results for 1 concatenation (JIT was not yet doing its job), even for 10 concatenations the performance penalty is relevant; for thousands of concatenations, the difference is huge.

Lessons learned from this very quick experiment (easily reproducible with the above code): never use the += to concatenate strings together, even in very basic cases where a few concatenations are needed (as said, creating new strings is expensive anyway and puts pressure on the GC).


J
James McMahon

Apache Commons-Lang has a ToStringBuilder class which is super easy to use. It does a nice job of both handling the append-logic as well as formatting of how you want your toString to look.

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

Will return output that looks like com.blah.YourClass@abc1321f[a=whatever, b=foo].

Or in a more condensed form using chaining:

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

Or if you want to use reflection to include every field of the class:

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

You can also customize the style of the ToString if you want.


u
user1428716

I think we should go with StringBuilder append approach. Reason being :

The String concatenate will create a new string object each time (As String is immutable object) , so it will create 3 objects. With String builder only one object will created[StringBuilder is mutable] and the further string gets appended to it.


Why this answer is downvoted ? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Mutable Reduction
T
Thorbjørn Ravn Andersen

Make the toString method as readable as you possibly can!

The sole exception for this in my book is if you can prove to me that it consumes significant resources :) (Yes, this means profiling)

Also note that the Java 5 compiler generates faster code than the handwritten "StringBuffer" approach used in earlier versions of Java. If you use "+" this and future enhancements comes for free.


E
Eddy

There seems to be some debate whether using StringBuilder is still needed with current compilers. So I thought I'll give my 2 cents of experience.

I have a JDBC result set of 10k records (yes, I need all of them in one batch.) Using the + operator takes about 5 minutes on my machine with Java 1.8. Using stringBuilder.append("") takes less than a second for the same query.

So the difference is huge. Inside a loop StringBuilder is much faster.


I think the debate is about using it outside of loops. I think there is a consensus you need to use it inside a loop.
n
nagendra547

Here is what I checked in 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

It's really a nightmare if you are using string concatenation for large number of strings.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

N
N Droidev

I think this image would be very useful to compare all classes for working with Strings:

https://i.stack.imgur.com/Jvbvq.png


Could you tell which Java version did you use?
@wayne he copy pasted the image from this blog. By the looks of it's pom.xml, its Java 7.
@wayne I dont think that may differ so much.. Performance ratios will be the same in all versions probably.. I dont know version because I found it in a blog.
w
wayne

It's worth mentioning that as pointed out by @ZhekaKozlov,

+ is faster since Java 9, unless JVM doesn't know how to optimise it (e.g. concatenation in a loop).

I checked the bytecode for following code (in 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();
    }
}

For toStringPlus:

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

for 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

The + version simply invokes dynamic function makeConcatWithConstants and and pass in the method argument {a:\u0001, b:, c: } (\u0001 being the parameter placeholder).
Whereas the StringBuilder version has to do it the 'honest' way.
I guess we can see why is + faster now.


B
Brian Agnew

Can I point out that if you're going to iterate over a collection and use StringBuilder, you may want to check out Apache Commons Lang and StringUtils.join() (in different flavours) ?

Regardless of performance, it'll save you having to create StringBuilders and for loops for what seems like the millionth time.


S
Sudip Bhandari

Performance wise String concatenation using '+' is costlier because it has to make a whole new copy of String since Strings are immutable in java. This plays particular role if concatenation is very frequent, eg: inside a loop. Following is what my IDEA suggests when I attempt to do such a thing:

https://i.stack.imgur.com/Pbtde.jpg

General Rules:

Within a single string assignment, using String concatenation is fine.

If you're looping to build up a large block of character data, go for StringBuffer.

Using += on a String is always going to be less efficient than using a StringBuffer, so it should ring warning bells - but in certain cases the optimisation gained will be negligible compared with the readability issues, so use your common sense.

Here is a nice Jon Skeet blog around this topic.


You should never use StringBuffer unless you absolutely require synchronized access from multiple threads. Otherwise prefer StringBuilder which is not synchronized and thus has less overhead.
D
Droo

For simple strings like that I prefer to use

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

In order, I would say the preferred method of constructing a string is using StringBuilder, String#concat(), then the overloaded + operator. StringBuilder is a significant performance increase when working large strings just like using the + operator is a large decrease in performance (exponentially large decrease as the String size increases). The one problem with using .concat() is that it can throw NullPointerExceptions.


Using concat() is likely to perform worse than '+' since the JLS allows '+' to be converted to a StringBuilder, and most likely all JVM's do so or use a more efficient alternative - the same is not likely to be true of concat which must create and discard at least one complete intermediate string in your example.