Sunday, September 17, 2023

4 ways to concatenate Strings in Java [Example and Performance]

When we think about String Concatenation in Java, what comes to our mind is the + operator, one of the easiest ways to join two String or a String, and a numeric in Java. Since Java doesn't support operator overloading, it's pretty special for String to have behavior. But in truth, it is the worst way of concatenating String in Java. When you concatenate two String using + operator e.g. "" + 101, one of the popular ways to convert int to String, compiler internally translates that to StringBuilder append call, which results in the allocation of temporary objects. 

You can see the real difference in the performance of our example program, in which we have concatenated 100,000 String using the + operator. Anyway, this article is not just about + operator but also about other ways to concatenate multiple Strings. 

There are four ways to do this, apart from the + operator, we can use StringBuffer, StringBuilder, and concat() method from java.lang.String class for the same purpose.

Both StringBuilder and StringBuffer classes are there for just this reason, and you can see that in our performance comparison. StringBuilder is the winner and the fastest way to concatenate Strings. StringBuffer is a close second, because of the synchronized method, and the rest of them are just 1000 times slower than them. 

4 ways to concatenate String in Java

Here we will see examples of all four ways of concatenating Strings in Java. Starting with the most popular approach, joining string using + operator. 

1. String Concatenation using + Operator

The easiest way of joining multiple String and numeric values in Java. Just remember that, when you have two or more primitive type values e.g. char, short, or int, at the beginning of your string concatenation, you need to explicitly convert the first of them to a String. 

For example System.out.println(200 + 'B' + ""); will not print 200B, instead, it will print 266, by taking ASCII values of 'B' and adding them to 200.


On the other hand System.out.println("" + 200 + 'B') will print 200B. Unfortunately, this is the worst way to concatenate String in Java. Let's take a look at how the String concatenation operator works in Java. When we write :

String temp = "" + 200 + 'B';

it's translated into

new StringBuilder().append( "" ).append( 200 ).append('B').toString();

All + operator are translated into several StringBuilder.append() calls before final toString() call.

Since StringBuilder(String) constructor allocates a buffer with only 16 char, appending more than 16 characters will require buffer reallocation. At last, StringBuffer.toString() calls create a new String object with a copy of the StringBuilder buffer.

This means, to concatenate two String, you will need to allocate, one StringBuilder, one char array[16], one String object and another char[] of appropriate size to fit your input value. Imagine if you are doing these thousands of times in your application, it's not only slow but also increases the workload of Garbage Collector.

You can also see Java Performance The Definitive Guide By Scott Oaks to learn more about how to do performance testing in Java and how to tune different things in the Java ecosystem to get the best performance from your Java application.


2. Using concat() method from java.lang.String

Concat(String str) method concatenates the specified String to the end of this string. I have hardly seen this used for concatenation, though. Inside, it allocates a char[] of length equal to the combined length of two String, copies String data into it, and creates a new String object using private String constructor, which doesn't make a copy of input char[], as shown below.

 public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);

    }

  /*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */

    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;

    }

The performance of concat() method is similar to the + operator and I don't recommend using it for any production purpose.


3. Using StringBuffer

This was the proper way to concatenate multiple String, integer, and others prior to Java 5 when StringBuilder was not available. It's much faster than the + operator and concert() method. The only drawback of this class is that all its append methods are synchronized

Since we hardly share temporary string in a concurrent environment, the price for thread-safety is not desired in many cases, that's where StringBuilder is used.

It works similarly to StringBuilder synchronization. It also provides several overloaded append() methods to concatenate integer, char, short, etc.

4. Using StringBuilder

This is the best way to concatenate String in Java, especially when you are concatenating multiple Strings. It's not thread-safe, but you hardly need that during String concatenation. In the next section, we will see a performance comparison of all these 4 ways to concatenate String in Java.

How to concatenate multiple String in Java



From JDK 8 onwards, you can also use StringJoiner to concatenate in Java. I am not including StringJoiner in this article but you can also these 10 examples of StringJoiner to learn more about it. 


Performance comparison + Operator vs Concat vs StringBuffer vs StringBuilder

here is a sample Java program to find out which method gives us best performance for String concatenation.

public class Demo{

    public static void main(String args[]) throws IOException {
        final int ITERATION = 100_000;
        String s = "";

        // String Concatenation using + operator
        long startTime = System.nanoTime();
        for (int i = 0; i < ITERATION; i++) {
            s = s + Integer.toString(i);
        }
        long duration = (System.nanoTime() - startTime) / 1000;
        System.out.println("Time taken to concatenate 100000
            Strings using + operator (in micro) : " + duration);

        // Using String concat() method
        startTime = System.nanoTime();
        for (int i = 0; i < ITERATION; i++) {
            s.concat(Integer.toString(i));

        }
        duration = (System.nanoTime() - startTime) / 1000;
        System.out.println("Time taken to concatenate 100000 
         Strings using concat method (in micro) : " + duration);
        
    // StringBuffer example to concate String in Java
        StringBuffer buffer = new StringBuffer(); // default size 16
        startTime = System.nanoTime();
        for (int i = 0; i < ITERATION; i++) {
            buffer.append(i);
        }

        duration = (System.nanoTime() - startTime) / 1000;
        System.out.println("Time taken to concatenate 
          100000 Strings using StringBuffer (in micro) : " + duration);

        // StringBuilder example to concate two String in Java
        StringBuilder builder = new StringBuilder(); 
        //default size for worst case

        startTime = System.nanoTime();
        for (int i = 0; i < ITERATION; i++) {
            builder.append(i);
        }
        duration = (System.nanoTime() - startTime) / 1000;
        System.out.println("Time taken to concatenate 
        100000 Strings using StringBuilder append in micro) : " + duration);
    }
}

Output:
Time taken to concatenate 100000 Strings using + operator (in micro) 
: 29178349
Time taken to concatenate 100000 Strings using concat method (in micro) 
: 21319431
Time taken to concatenate 100000 Strings using StringBuffer (in micro) : 12557
Time taken to concatenate 100000 Strings using StringBuilder append (in micro) 
: 10641

You can clearly see that given everything the same, StringBuilder outperforms all others. It's almost 3000 times faster than + operator. concat() method is better than + operator but still very bad compared to StringBuffer and StringBuilder. 

StringBuffer took more time than StringBuilder because of synchronized method. I know that, you hardly join 100K Strings but even for small numbers it adds. if you run program to join just 10 Strings, you will notice significant difference in time spent Here is what I got when I ran this program for 10 iteration only :

+ operator took (in micros) : 177
concat() method took (in micros) : 28
StringBuffer took (in micros) : 21
StringBuilder append took (in micros) : 8

Though using the + operator along with empty String is easiest and obvious way to concatenate multiple String and numeric in Java, you should not use that, especially inside loops. Always use StringBuilder for string concatenation, the class is there for a reason, and if you are still using StringBuffer then get rid of that, because most of the time you don't really need the thread-safety overhead of StringBuffer.  

Unfortunately, concat() method is only good when you need to concatenate exactly 2 strings. In short, always use StringBuilder to concatenate Strings in Java.

13 comments:

  1. From Java >1.6 plus operator is changed into StringBuilder by compiler. So what is the point in making code less readable by StringBuilder?

    ReplyDelete
    Replies
    1. @Michal, there are cases where using + operator is not optimal e.g. concatenating in loop, because Java will internally create and destroy StringBuilder instances. Sometime its better to have one StringBuilder object outside loop and just append on it while iterating over loop, instead of using + operator.

      Delete
    2. Yes there are cases, but these will make about 0.000000000001% of cases; making the explicit use of stringbuilder just a waste of time (and an unneeded premature optimization)

      Delete
    3. It depends on one's preferences and/or string you are creating but using StringBuilder might be far more readable than + operator.

      Delete
  2. I ran your code and got similar results. However, when I added a couple more cases, I was surprised:

    Case 1 - building a new string using '+'. (s = "one" + 2 + 3.0;)
    Case 2 - build a new string using StringBuilder (s = new StringBuilder().append("one").append(2).append(3.0).toString();)

    These results were MUCH different from your examples of *appending* to an existing string. In fact, using the '+' method was 100 times faster!

    ReplyDelete
  3. Hi, I added the String.join method like this:
    startTime = System.nanoTime();
    for (int i = 0; i < ITERATION; i++) {
    s = String.join(" ", Integer.toString(i));
    }
    duration = (System.nanoTime() - startTime) / 1000;
    System.out
    .println("Time taken to concatenate 100000 Strings using String.join in micro) : " + duration);

    it was much faster than concat and closer to the StringBuilder

    ReplyDelete
  4. final int ITERATION = 100_0;
    its giving strange result. Making StringBuffer fastest ?

    Time taken to concatenate 100000 Strings using + operator (in micro) : 9053
    Time taken to concatenate 100000 Strings using concat method (in micro) : 3796
    Time taken to concatenate 100000 Strings using StringBuffer (in micro) : 469
    Time taken to concatenate 100000 Strings using StringBuilder append in micro) : 485

    ReplyDelete
  5. Hello Shashank, which version of JDK you are using? StringBuffer is not really a problem in single threaded environment because uncontented lock acquisition has become faster with latest JVM and JIT version.

    ReplyDelete
  6. Any analysis on String.format?

    ReplyDelete
  7. A few corrections to make the test fair.

    1. Both StringBuilder and StringBuffer need to produce their merged string result by calling toString() inside the timing fence.

    2. The concat call also needs to save its result as in:
    s = s.concat(i);

    3. The working String s needs to be reset prior to each test that uses it.

    s = "";

    4. Appending an integer to a String is not a good test of the string functions. It is possible a bulk of the time measured will be the conversion of the integer to a string. This conversion is constant on all the tests. I would recommend appending a constant String value rather then a converted integer.

    5. I find StringBuilder surprisingly inefficient with its memory management. It is filling a regrowing array of character array rather then a single pass merging all of the results. If you want to reduce your memory hit and your source strings are persistent (not coming from a file or stream), you may want an alternate solution like:

    http://landenlabs.com/android/weakness/java-weakness.html#java.stringbuilder

    Lastly - I find all good benchmarks should verify the functions are producing identical results. The testing should include the display of a hash code of the function results.


    ReplyDelete
  8. how to concat

    (what)

    and (is java?)

    so that the output will be as ( what is java )...

    ReplyDelete
  9. In method 2, what is variable value refers to ?

    ReplyDelete
    Replies
    1. Hello giri, it refers to instance variable value in the String class. See the full code from java.lang.String class in Eclipse.

      Delete

Feel free to comment, ask questions if you have any doubt.