StringBuilder vs String concatenation in toString() in Java

441,449

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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
Share:
441,449
non sequitor
Author by

non sequitor

Updated on April 28, 2022

Comments

  • non sequitor
    non sequitor about 2 years

    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?

    • ewall
      ewall over 14 years
      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.
    • Asif Mushtaq
      Asif Mushtaq about 8 years
      what is the mean of 100 in parameter?
    • non sequitor
      non sequitor about 8 years
      @UnKnown 100 is the initial size of StringBuilder
    • Asif Mushtaq
      Asif Mushtaq about 8 years
      @nonsequitor So the maximum characters will be 100?
    • non sequitor
      non sequitor about 8 years
      @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.
    • M A
      M A almost 8 years
    • vuhung3990
      vuhung3990 almost 6 years
  • bruno conde
    bruno conde over 14 years
    That's true but the language reference also states that this is optional. In fact, I just did a simple test with JRE 1.6.0_15 and I didn't see any compiler optimization in the decompiled class.
  • Brett
    Brett over 14 years
    It's actually longer, contains more symbols and has variables a text out of sequence.
  • Michael Borgwardt
    Michael Borgwardt over 14 years
    I Just tried the code from the question (compiled on JDK 1.6.0_16) and found the optimization as expected. I'm pretty sure all modern compilers will do it.
  • Droo
    Droo over 14 years
    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.
  • tangens
    tangens over 14 years
    So would you say it's less readable than one of the other aproaches?
  • Alex Feinman
    Alex Feinman over 14 years
    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.
  • bruno conde
    bruno conde over 14 years
    You are correct. Looking at the bytecode I can clearly see the StringBuilder optimization. I was using a decompiler and, some how, it is converting back to concat. +1
  • Michael Borgwardt
    Michael Borgwardt over 14 years
    I guess the decompiler tried to be extra smart in restoring the original source code :) I used (a probably old version of) JAD.
  • Lawrence Dol
    Lawrence Dol over 14 years
    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.
  • Steve Kuo
    Steve Kuo over 14 years
    Seems harder to read (for me). Now I have to scan back and forth between {...} and the parameters.
  • Blindy
    Blindy almost 14 years
    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.
  • Paŭlo Ebermann
    Paŭlo Ebermann over 13 years
    The single difference is the size parameter given in the initialization.
  • Milan Aleksić
    Milan Aleksić almost 13 years
    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 concatenation
  • perilbrain
    perilbrain over 12 years
    @Blindy & Droo :- You both are right.Using .concate in such scenario is best workaround,as += creates new object every time loop routine executes.
  • Dandre Allison
    Dandre Allison almost 12 years
    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.
  • Lucas
    Lucas almost 12 years
    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?
  • sleske
    sleske over 11 years
    @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.
  • rds
    rds over 11 years
    I prefer this form to, because it is safe if one of the parameter is null
  • Omry Yadan
    Omry Yadan about 11 years
    do you know that his toString() is not called in a loop?
  • Marcin
    Marcin about 11 years
    @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.
  • lamarmora
    lamarmora about 11 years
    Thanks, is it true also for Visual Basic?
  • Ernestas Gruodis
    Ernestas Gruodis over 10 years
    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.
  • Olga
    Olga over 10 years
    Yes, StringBuilder doesn't need to re-create the String object over and over.
  • Raffael
    Raffael over 10 years
    The byte code can be seen here: bpaste.net/show/Z8o0hdiCerkFUXhaeU7L note that the line numbers don't match, but it IS the correct code
  • user1722791
    user1722791 over 10 years
    Dammit I used those 2 function to test a big string I'm working in. 6.51min vs 11secs
  • Geek
    Geek almost 10 years
    @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?
  • vach
    vach almost 10 years
    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?
  • Michael Borgwardt
    Michael Borgwardt almost 10 years
    @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.
  • Arkantos
    Arkantos almost 10 years
    Is there any tool to show this + to String Builder translation ? I know its evident from the bytecode but if there's any tool that can show this, that would be pretty neat :)
  • Pacerier
    Pacerier over 9 years
    @Arkantos, javap is the exact tool you need. See stackoverflow.com/a/47628/632951
  • randers
    randers almost 9 years
    By the way you can use result += s; as well (in the first example)
  • vancexu
    vancexu over 8 years
    @Laurent blog post is worth reading. It explains why StringBuilder is faster, do we need it in Java 8 etc... in detail. Highly recommend.
  • Navin
    Navin over 8 years
    @rds Nope, plain concatenation a+b does the exact same thing as format if b is null. Both methods result in appending the string "null".
  • rds
    rds over 8 years
    @Navin indeed. null + "issimo" produces "nullissimo".
  • cellepo
    cellepo over 8 years
    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).
  • Asif Mushtaq
    Asif Mushtaq about 8 years
    how many object will be created by this statement? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
  • Gullbyrd
    Gullbyrd about 8 years
    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.
  • joel.neely
    joel.neely about 8 years
    @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).
  • Ced
    Ced almost 8 years
    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 mad
  • amitfr
    amitfr almost 8 years
    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?
  • Christophe Roussy
    Christophe Roussy almost 8 years
    Is this Oracle JDK or OpenJDK ?
  • Christophe Roussy
    Christophe Roussy almost 8 years
    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).
  • Adowrath
    Adowrath over 7 years
    @amitfr 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.
  • joel.neely
    joel.neely over 7 years
    @amitfr 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.
  • joel.neely
    joel.neely over 7 years
    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).
  • joel.neely
    joel.neely over 7 years
    @Ced 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".
  • Ced
    Ced over 7 years
    @joel.neely Yeah it might be easier once you get used to it
  • CptBartender
    CptBartender about 7 years
    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.
  • Jordan
    Jordan almost 7 years
    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.
  • jitendra varshney
    jitendra varshney almost 7 years
    String.concat about twice as fast as StringBuffer.append
  • Omry Yadan
    Omry Yadan almost 7 years
    may very well be, this answer is before String.concat existed. but do demonstrate with code instead of words :)
  • deFreitas
    deFreitas over 6 years
    That's a example when you cannot expect compiler optimizations, and the why you must to be careful about your code
  • deFreitas
    deFreitas over 6 years
    I ask myself why the compiler or JIT doesn't optimize it in this case.
  • Ashish Lohia
    Ashish Lohia over 6 years
    The time difference is coming because your code for loop inside slow() compiles to this-- str = new StringBuilder().append(str).append("*").toString();
  • Ashish Lohia
    Ashish Lohia over 6 years
    Concatenation is by default compiled to StringBuilder only. But, in case of loops the toString is called multiple times.
  • spyro
    spyro over 6 years
    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."
  • Ryan Newsom
    Ryan Newsom over 6 years
    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.
  • ErikE
    ErikE almost 6 years
    Too bad it's not C#: $"{a}, {b}, {c}". :)
  • Erki der Loony
    Erki der Loony almost 5 years
    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.
  • user1428716
    user1428716 over 4 years
    This answer needs modification - If you go by the mutable containers logic - StringBuilder is better suited for Large Operations.
  • user1428716
    user1428716 over 4 years
    Why this answer is downvoted ? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Mutable Reduction
  • user1428716
    user1428716 over 4 years
    This should be the accepted answer .. it depends on the size of String Stream that determines the choice of concat or StringBuilder
  • anegru
    anegru over 4 years
    I used String to concatenate 18MB of data with Java 1.8. The algorithm took 15 minutes. I changed String to StringBuilder and the same algorithm took 500 milliseconds. So there is NO guarantee that the compiler would optimize.
  • Michael Borgwardt
    Michael Borgwardt over 4 years
    @AdrianNegru: read the entire answer. Including the last sentence.
  • Louis Wasserman
    Louis Wasserman over 3 years
    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.)
  • ChuckZHB
    ChuckZHB about 3 years
    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-parentheses
  • sunzy
    sunzy almost 3 years
    why JVM compile it like this ?
  • Holger
    Holger over 2 years
    @joel.neely 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.
  • Holger
    Holger over 2 years
    @spyro 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.
  • Holger
    Holger over 2 years
    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.
  • Holger
    Holger over 2 years
    @ChristopheRoussy doesn’t matter as they consist of exactly the same code.
  • Christophe Roussy
    Christophe Roussy over 2 years
    @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.
  • Holger
    Holger over 2 years
    @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”
  • wayne
    wayne about 2 years
    Could you tell which Java version did you use?
  • Shayan Ahmad
    Shayan Ahmad about 2 years
    @wayne he copy pasted the image from this blog. By the looks of it's pom.xml, its Java 7.
  • N Droidev
    N Droidev about 2 years
    @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.
  • klarki
    klarki about 2 years
    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
  • Marinos An
    Marinos An about 2 years
    @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().