StringBuilder vs String concatenation in toString() in Java
Asked Answered
P

20

1120

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?

Presentational answered 7/10, 2009 at 15:44 Comment(7)
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.Toro
what is the mean of 100 in parameter?Audraaudras
@UnKnown 100 is the initial size of StringBuilderPresentational
@nonsequitor So the maximum characters will be 100?Audraaudras
@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.Presentational
Related documentation: stackoverflow.com/documentation/java/109/strings/5280/…Girlfriend
it's same intellij-support.jetbrains.com/hc/en-us/community/posts/…Hedva
C
1129

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.

Chlor answered 7/10, 2009 at 15:51 Comment(13)
the difference between two most used decompilers exists for string concatenation: JAD shows StringBuilder, JD-GUI shows concatenation. If you open the .class file in any editor you will see StringBuilder references, thus the optimization will be done, it's just JD-GUI showing it in more "reader-friendly" way - as concatenationAffirm
I believe one more consideration in the optimization of using StringBuilder vs concatenating (outside of a loop) is whether you are constructing multiple Strings. Is the compiler smart enough to reuse the StringBuilder it adds with setLength(0)? If it doesn't then you can do it manually instead of the compiler optimizing the concatenation but missing the optimization of reusing the StringBuilder.Tobacco
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?Tancred
@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.Waltner
@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.Mythologize
@MichaelBorgwardt I have a follow-up question on the last paragraph of your answer. So why can't the compiler optimize the loop case? You said usually it can't. Can you please explain?Mckenziemckeon
Guys in that documentation it says StringBuffer not StringBuilder and buffer definitely is slower than builder as it is synchronized and every operation is adding lock aquireing and releasing operations with it... Do i understand it as it is? or i'm missing something?Lavernalaverne
@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.Chlor
@Laurent blog post is worth reading. It explains why StringBuilder is faster, do we need it in Java 8 etc... in detail. Highly recommend.Nickell
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.Aggappera
This may not be true any more and for all cases - by default, StringBuilder is initialized with a capacity of 16. If the dynamic arguments are sufficiently long, then the entire string may end up being longer, in which case each subsequent append would require internal array resizing, which in turn would impact the performance. The second approach, however, initializes the builder with a capacity of 100, which vastly increases the potential length of arguments printed, and thus does not impact the performance if more cases.Ardeb
Note that as of Java 9, if you're just concatenating a single String and not doing repeated concatenations, concatenation with + can turn into something better than anything you could handwrite, by e.g. accessing String internals that external code can't get to. This is a result of JEP 280 and the default use of the mh_inline_sized_exact strategy. (This is also a good demonstration of why you should trust the compiler to do the right thing.)Globeflower
I try it on a recursive algorithm, using String concatenation in each recursion vs StringBuilder. StringBuilder approach is faster than String concatenation approach in this scenario. leetcode.com/problems/generate-parenthesesBlakely
V
305

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();
Viminal answered 7/10, 2009 at 15:47 Comment(9)
Yes, StringBuilder doesn't need to re-create the String object over and over.Congratulation
Dammit I used those 2 function to test a big string I'm working in. 6.51min vs 11secsAedes
By the way you can use result += s; as well (in the first example)Exarch
how many object will be created by this statement? "{a:"+ a + ", b:" + b + ", c: " + c +"}";Audraaudras
@UnKnown: Just one. If you compile it and look at the byte code, you'll see one StringBuilder instantiation, a series of append calls, and one toString call (the exact sequence is too long for me to paste into a comment).Viminal
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?Candlemaker
@Candlemaker I actually checked it (lookup javap, you can analyze bytecode with it) and, with a slightly modified example (String str = ((a == null) ? a1 : a) + ((b == null) ? b1 : b) + ((c == c1) ? null : c);), it still produced the optimizations, Running with a javac 1.8.0_102.Dominga
@Candlemaker I would go back to my original criterion: "...whether you are writing a single concatenation..." Although you have sub-expressions (which happen to contain ternary operators), the entire concatenation process is in a single statement, knowable at compile time.Viminal
why JVM compile it like this ?Ghent
H
91

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.

Hinson answered 7/10, 2009 at 15:58 Comment(9)
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.Endure
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.Trutko
@Trutko & Droo :- You both are right.Using .concate in such scenario is best workaround,as += creates new object every time loop routine executes.Xerophthalmia
do you know that his toString() is not called in a loop?Hinson
String.concat about twice as fast as StringBuffer.appendBreak
may very well be, this answer is before String.concat existed. but do demonstrate with code instead of words :)Hinson
The time difference is coming because your code for loop inside slow() compiles to this-- str = new StringBuilder().append(str).append("*").toString();Necking
Concatenation is by default compiled to StringBuilder only. But, in case of loops the toString is called multiple times.Necking
I ran the benchmarks for 10k, and modified slow case with addition as s = "*" + "*" + ....10k times, and got 0ms (slow) vs 3ms (fast).Escargot
E
76

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.

Eboni answered 7/10, 2009 at 15:47 Comment(22)
It's actually longer, contains more symbols and has variables a text out of sequence.Telltale
So would you say it's less readable than one of the other aproaches?Eboni
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.Jarvey
Seems harder to read (for me). Now I have to scan back and forth between {...} and the parameters.Mullion
I prefer this form to, because it is safe if one of the parameter is nullRivulet
@Rivulet Nope, plain concatenation a+b does the exact same thing as format if b is null. Both methods result in appending the string "null".Exercitation
@Exercitation indeed. null + "issimo" produces "nullissimo".Rivulet
This is less readable because there are more chars to read, compared to the Question's option 1, and there is additional complexity to understand the format function (compared to literal concatenation).Holland
I really hate this. For those of us that don't use it it's harder to process than a simple +. Beside, you can't read from left to right like in plain English. Goddamnit. I'm madSchaub
It is more readable in some cases but you can easily forget one of the %s or params and should verify the whole code using FindBugs or some IDE feature (like for the logs).Thirtytwomo
I use String.format on occasion (which, remember, was not available in early versions of Java). There are some advantages over simple concatenation: the format can be re-used, it allows straightforward control of widths (including fractional digits), once you become used to the % notation, it can be easier to visualize the result (especially when rendering with combinations of punctuation).Viminal
@Schaub YMMV, but I read it from left to right by thinking of the format string as a stencil and the subsequent expressions as paint/contents. So... "Format/fill [into] this string [with] these values".Viminal
@Viminal Yeah it might be easier once you get used to itSchaub
If Oracle could finally move it's butt, they just could support string interpolaton like most other modern languages do: "Hello $name, you have $messages.size() questions."Eisk
String.format is not short nor readable(imo). I remember learning this in CS1 and going wtf? I much prefer concatenation or even the use of StringBuilder. Maybe someday we will be blessed with String interpolation in Java.Cisalpine
Too bad it's not C#: $"{a}, {b}, {c}". :)Calmas
@Viminal how can the format be reused? You can use the same format string or a different one, it doesn’t matter at all, as the format string gets re-parsed every time, format is executed, even for repeated executions of the same expression. There is no way to keep and reuse an already parsed format in this API.Duplicature
@Eisk there’s little advantage in "Hello $name, you have $messages.size() questions." over "Hello "+name+", you have "+messages.size()+" questions.". In fact, the latter is clearer as every reader immediately recognizes that .size() is part of the expression to evaluate, rather than part of the string.Duplicature
@Holger: I simply meant that the format is a specification, and extracting it as a constant (instead of cut-and-paste) allows one to ensure that a single update suffices for all uses (instead of having to update multiple literals with the risk of missing/mistyping one).Viminal
@Viminal with the huge disadvantage that the central place can not ensure that all use sites pass the correct parameters for the format string—especially when you make changes to it afterwards. Obviously, it is much better to declare a shared method instead of a shared format string. The method can declare an explicit parameter list rather than varargs of unspecific Object, enforcing compatibility with all callers. At the same time, it eliminates the need for a shared format string.Duplicature
Since Java 15, we can also write: "{a: %s, b: %s, c: %s}".formatted(a, b, c).Chronologist
And as a preview feature in Java 20, hopefully included for real in a later version, we can use string interpolation: STR."{a: \{a}, b: \{b}, c: \{c}"Chronologist
T
34

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
Timehonored answered 16/4, 2012 at 14:14 Comment(1)
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.Duplicature
X
29

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.

Xerophthalmia answered 21/12, 2011 at 20:29 Comment(0)
C
23

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.

Compo answered 26/5, 2015 at 7:15 Comment(4)
Is this Oracle JDK or OpenJDK ?Thirtytwomo
@ChristopheRoussy doesn’t matter as they consist of exactly the same code.Duplicature
@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.Thirtytwomo
@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”Duplicature
H
23

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

Heyman answered 31/10, 2017 at 15:49 Comment(4)
This should be the accepted answer .. it depends on the size of String Stream that determines the choice of concat or StringBuilderElectrophone
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 @wayneDragline
@Dragline 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().Heyman
For Java 11, it is : [str1.concat(str2)] Created string of length:1950000 in 4486 ms [str1+=str2] Created string of length:1950000 in 5582 ms [str1.append(str2)] Created string of length:1950000 in 36 msShenika
L
18

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.

Lillia answered 3/4, 2017 at 0:53 Comment(0)
F
9

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

Foliole answered 21/4, 2016 at 15:11 Comment(0)
V
7

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.

Vitrain answered 7/10, 2009 at 16:9 Comment(0)
I
6

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

  1. The String concatenate will create a new string object each time (As String is immutable object) , so it will create 3 objects.

  2. With String builder only one object will created[StringBuilder is mutable] and the further string gets appended to it.

Inoperable answered 24/7, 2017 at 13:39 Comment(1)
Why this answer is downvoted ? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Mutable ReductionElectrophone
L
4

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.

Lord answered 7/10, 2009 at 19:5 Comment(0)
D
3

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.

Durgy answered 24/4, 2017 at 8:22 Comment(1)
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.Hook
V
3

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<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }
    

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

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
Vat answered 28/7, 2019 at 14:32 Comment(0)
K
3

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

enter image description here

Knop answered 21/10, 2021 at 22:13 Comment(4)
Could you tell which Java version did you use?Drank
@Drank he copy pasted the image from this blog. By the looks of it's pom.xml, its Java 7.Augean
@Drank 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.Knop
@user16320675 This statistics belongs to Java 7.Knop
D
3

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.

Drank answered 26/4, 2022 at 8:56 Comment(0)
Y
1

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.

Ymir answered 7/10, 2009 at 16:2 Comment(0)
C
1

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:

enter image description here

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.

Culprit answered 24/1, 2018 at 13:49 Comment(1)
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.Sambar
E
-4

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.

Endure answered 7/10, 2009 at 16:11 Comment(1)
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.Footlocker

© 2022 - 2024 — McMap. All rights reserved.