String concatenation: concat() vs "+" operator
Asked Answered
S

12

592

Assuming String a and b:

a += b
a = a.concat(b)

Under the hood, are they the same thing?

Here is concat decompiled as reference. I'd like to be able to decompile the + operator as well to see what that does.

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}
Sallysallyann answered 6/9, 2008 at 16:8 Comment(5)
possible duplicate of StringBuilder vs String concatenation in toString() in JavaChavannes
I'm not sure + can be decompiled.Ez
Use javap to disassemble a Java class file.Conall
Due to 'immutability' you should probably be using StringBuffer or StringBuilder-(thread unsafe thus faster, insteadAttune
dzone.com/articles/concatenating-strings-in-java-9Unprejudiced
S
649

No, not quite.

Firstly, there's a slight difference in semantics. If a is null, then a.concat(b) throws a NullPointerException but a+=b will treat the original value of a as if it were null. Furthermore, the concat() method only accepts String values while the + operator will silently convert the argument to a String (using the toString() method for objects). So the concat() method is more strict in what it accepts.

To look under the hood, write a simple class with a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

Now disassemble with javap -c (included in the Sun JDK). You should see a listing including:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

So, a += b is the equivalent of

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

The concat method should be faster. However, with more strings the StringBuilder method wins, at least in terms of performance.

The source code of String and StringBuilder (and its package-private base class) is available in src.zip of the Sun JDK. You can see that you are building up a char array (resizing as necessary) and then throwing it away when you create the final String. In practice memory allocation is surprisingly fast.

Update: As Pawel Adamski notes, performance has changed in more recent HotSpot. javac still produces exactly the same code, but the bytecode compiler cheats. Simple testing entirely fails because the entire body of code is thrown away. Summing System.identityHashCode (not String.hashCode) shows the StringBuffer code has a slight advantage. Subject to change when the next update is released, or if you use a different JVM. From @lukaseder, a list of HotSpot JVM intrinsics.

Stanislaus answered 6/9, 2008 at 16:25 Comment(13)
@HyperLink You can see the code using javap -c on a compiled class that uses it. (Oh, as in the answer. You just need to interpret the bytecode disassembly, which shouldn't be that difficult.)Stanislaus
You can consult the JVM spec to understand the individual bytecodes. The stuff you'd want to reference is in chapter 6. A bit obscure, but you can get the gist of it fairly easily.Conall
I wonder why the Java compiler uses StringBuilder even when joining two strings? If String included static methods to concatenate up to four strings, or all the strings in a String[], code could append up to four strings with two object allocations (the result String and its backing char[], neither one redundant), and any number of strings with three allocations (the String[], the result String, and the backing char[], with only the first being redundant). As it is, using StringBuilder will at best require four allocations, and will require copying every character twice.Compulsory
That expression, a+=b. Doesn't it mean: a=a+b?Faught
@user132522 Yes. (Although with other types there may be casting involved.)Stanislaus
How does it have to do with concatenation? You mean a being a string object, and b is of other type. So b gets forced to become a string object?Faught
Is this what you mean by casting? I am at elementary lvl. I usually use the word "casting" for converting between int and double(truncation).Faught
@user132522 For types such as short the result of + would be int (because that's the sort of weird thing Java does). So a = a + b; would not compile as you can't assign an int to a short. a += b; would silently cast back to short.Stanislaus
So int = short + short will not compiled. But b/c the shorthand cast the int result to short. In the format short+=short, the same expression will compile. Am I understanding it right?Faught
About that StringBuilder is better than concat: Correct me if i am wrong, but StringBuilder, and others of his kind allocates 2x the memory for receiving new content that exceeds the capacity need. Building SQL querys i got several times an error, for this reason in tests, and i solved using ´concat´, was a little bit les confortable but the memory was good.Tree
Things have changed since when this answer was created. Please read my answer bellow.Topology
Could u elaborate more about "bytecode compiler cheats" part?Ioneionesco
Things have changed since this answer was updated. I don't believe there's any scenario remaining in which StringBuilder or concat beat + due to JEP 280.Pendley
C
103

Niyaz is correct, but it's also worth noting that the special + operator can be converted into something more efficient by the Java compiler. Java has a StringBuilder class which represents a non-thread-safe, mutable String. When performing a bunch of String concatenations, the Java compiler silently converts

String a = b + c + d;

into

String a = new StringBuilder(b).append(c).append(d).toString();

which for large strings is significantly more efficient. As far as I know, this does not happen when you use the concat method.

However, the concat method is more efficient when concatenating an empty String onto an existing String. In this case, the JVM does not need to create a new String object and can simply return the existing one. See the concat documentation to confirm this.

So if you're super-concerned about efficiency then you should use the concat method when concatenating possibly-empty Strings, and use + otherwise. However, the performance difference should be negligible and you probably shouldn't ever worry about this.

Caramel answered 6/9, 2008 at 16:24 Comment(3)
concat infact doesn't do that. I've edited my post with a decompilation of the concat methodSallysallyann
infact it does. Look a the first lines of your concat code. The problem with concat is that it always generates a new String()Arleanarlee
@MarcioAguiar: maybe you mean that + always generates a new String - as you say, concat has one exception when you concat an empty String.Hensley
G
52

I ran a similar test as @marcio but with the following loop instead:

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

Just for good measure, I threw in StringBuilder.append() as well. Each test was run 10 times, with 100k reps for each run. Here are the results:

  • StringBuilder wins hands down. The clock time result was 0 for most the runs, and the longest took 16ms.
  • a += b takes about 40000ms (40s) for each run.
  • concat only requires 10000ms (10s) per run.

I haven't decompiled the class to see the internals or run it through profiler yet, but I suspect a += b spends much of the time creating new objects of StringBuilder and then converting them back to String.

Gold answered 6/9, 2008 at 19:25 Comment(4)
Object creation time really matters. That's why in many situations we use StringBuilder directly rather than taking advantage of the StringBuilder behind +.Interfluve
@coolcfan: When + is used for two strings, are there any cases where using StringBuilder is better than would be String.valueOf(s1).concat(s2)? Any idea why compilers wouldn't use the latter [or else omit the valueOf call in cases where s1 is known to be non-null]?Compulsory
@Compulsory sorry I don't know. Maybe people who are behind this sugar are the best ones to answer this.Interfluve
Search for: invokedynamic StringConcatFactoryDecrescendo
S
40

Most answers here are from 2008. It looks that things have changed over the time. My latest benchmarks made with JMH shows that on Java 8 + is around two times faster than concat.

My benchmark:

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "!@#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

Results:

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s
Slam answered 29/9, 2017 at 8:56 Comment(7)
I wonder why Java String never included a static function to form a string by concatenating the elements of a String[]. Using + to concatenate 8 strings using such a function would require constructing and later abandoning String[8], but that would be the only object that would need to be constructed abandoned, while using a StringBuilder would require constructing and abandoning StringBuilder instance and at least one char[] backing store.Compulsory
@Compulsory Some static String.join() methods were added in Java 8, as quick syntax wrappers around the java.util.StringJoiner class.Silicium
@TiStrga: Has the handling of + changed to use such functions?Compulsory
@Compulsory That would break binary backwards compatibility, so no. It was merely in reply to your "why String never included a static function" comment: now there is such a function. The rest of your proposal (refactoring + to use it) would require more than what the Java devs are willing to change, sadly.Silicium
@TiStrga: Is there any way a Java bytecode file can indicate "If function X is available, call it; otherwise do something else" in a way that could be resolved in the process of loading a class? Generating code with a static method that can either chain to Java's static method or else use a stringbuilder if that isn't available would seem the optimal solution.Compulsory
Could u tell me why + is fast 2x than StringBuilder?Ioneionesco
Things have changed again since Java 9. Please update.Muniz
S
22

Tom is correct in describing exactly what the + operator does. It creates a temporary StringBuilder, appends the parts, and finishes with toString().

However, all of the answers so far are ignoring the effects of HotSpot runtime optimizations. Specifically, these temporary operations are recognized as a common pattern and are replaced with more efficient machine code at run-time.

@marcio: You've created a micro-benchmark; with modern JVM's this is not a valid way to profile code.

The reason run-time optimization matters is that many of these differences in code -- even including object-creation -- are completely different once HotSpot gets going. The only way to know for sure is profiling your code in situ.

Finally, all of these methods are in fact incredibly fast. This might be a case of premature optimization. If you have code that concatenates strings a lot, the way to get maximum speed probably has nothing to do with which operators you choose and instead the algorithm you're using!

Simsar answered 6/9, 2008 at 18:38 Comment(2)
I guess by "these temporary operations" you mean the use of escape analysis to allocate "heap" objects on the stack where provable correct. Although escape analysis is present in HotSpot (useful for removing some synchronisation), I don't believe it, is at the time of writing, uStanislaus
While this topic is interesting, I really think the mention of "premature optimization" is important. Don't spend too much time on this issue if you're not sure this piece of code is actually taking an important proportion of the total computation time!Grandiose
A
21

How about some simple testing? Used the code below:

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • The "a + b" version executed in 2500ms.
  • The a.concat(b) executed in 1200ms.

Tested several times. The concat() version execution took half of the time on average.

This result surprised me because the concat() method always creates a new string (it returns a "new String(result)". It's well known that:

String a = new String("a") // more than 20 times slower than String a = "a"

Why wasn't the compiler capable of optimize the string creation in "a + b" code, knowing the it always resulted in the same string? It could avoid a new string creation. If you don't believe the statement above, test for your self.

Arleanarlee answered 6/9, 2008 at 17:59 Comment(1)
I tested on java jdk1.8.0_241 your code, For me the "a+b" code is giving optimized results. With concat(): 203ms and with "+": 113ms . I guess in previous release it wasn't that optimized.Defant
C
6

Basically, there are two important differences between + and the concat method.

  1. If you are using the concat method then you would only be able to concatenate strings while in case of the + operator, you can also concatenate the string with any data type.

    For Example:

    String s = 10 + "Hello";
    

    In this case, the output should be 10Hello.

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    In the above case you have to provide two strings mandatory.

  2. The second and main difference between + and concat is that:

    Case 1: Suppose I concat the same strings with concat operator in this way

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    In this case total number of objects created in the pool are 7 like this:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    Case 2:

    Now I am going to concatinate the same strings via + operator

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);
    

    In the above case total number of objects created are only 5.

    Actually when we concatinate the strings via + operator then it maintains a StringBuffer class to perform the same task as follows:-

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);
    

    In this way it will create only five objects.

So guys these are the basic differences between + and the concat method. Enjoy :)

Collimore answered 19/8, 2014 at 7:49 Comment(3)
My dear, You know very well that any string literal treated as an String object itself which stores in String pool.So in this case we have 4 string literals .So obviously at least 4 objects should be created in pool.Collimore
I don't think so: String s="I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern()); prints true, which means "good" was not in the string pool before calling intern()Vahe
I am talking only about this line String s="I"+"am"+"good"+"boy"; In this case all 4 are string literals are kept in a pool.Hence 4 objects should be created in pool.Collimore
C
5

For the sake of completeness, I wanted to add that the definition of the '+' operator can be found in the JLS SE8 15.18.1:

If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other operand to produce a string at run time.

The result of string concatenation is a reference to a String object that is the concatenation of the two operand strings. The characters of the left-hand operand precede the characters of the right-hand operand in the newly created string.

The String object is newly created (§12.5) unless the expression is a constant expression (§15.28)

About the implementation the JLS says the following:

An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. 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.

For primitive types, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.

So judging from the 'a Java compiler may use the StringBuffer class or a similar technique to reduce', different compilers could produce different byte-code.

Cartulary answered 26/3, 2017 at 15:0 Comment(0)
D
3

The + operator can work between a string and a string, char, integer, double or float data type value. It just converts the value to its string representation before concatenation.

The concat operator can only be done on and with strings. It checks for data type compatibility and throws an error, if they don't match.

Except this, the code you provided does the same stuff.

Divisible answered 6/9, 2008 at 16:16 Comment(0)
T
3

I don't think so.

a.concat(b) is implemented in String and I think the implementation didn't change much since early java machines. The + operation implementation depends on Java version and compiler. Currently + is implemented using StringBuffer to make the operation as fast as possible. Maybe in the future, this will change. In earlier versions of java + operation on Strings was much slower as it produced intermediate results.

I guess that += is implemented using + and similarly optimized.

Triploid answered 6/9, 2008 at 16:22 Comment(3)
"Currently + is implemented using StringBuffer" False It's StringBuilder. StringBuffer is the threadsafe impl of StringBuilder.Souse
It used to be StringBuffer prior to java 1.5, as that was the version when StringBuilder was first introduced.Backsword
When you say "currently" which JDK do you mean?Ointment
B
1

When using +, the speed decreases as the string's length increases, but when using concat, the speed is more stable, and the best option is using the StringBuilder class which has stable speed in order to do that.

I guess you can understand why. But the totally best way for creating long strings is using StringBuilder() and append(), either speed will be unacceptable.

Blus answered 10/2, 2015 at 9:51 Comment(3)
using the + operator is equivalent to using the StringBuilder (docs.oracle.com/javase/specs/jls/se8/html/…)Every
@Every or StringBufferIoneionesco
@Every "The implementation of the string concatenation operator is left to the discretion of a Java compiler, as long as the compiler ultimately conforms to The Java™ Language Specification. For example, the javac compiler may implement the operator with StringBuffer, StringBuilder, or java.lang.invoke.StringConcatFactory depending on the JDK version (...)." docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/…Horsepower
Z
0

Note that s.concat("hello"); would result in a NullPointereException when s is null. In Java, the behavior of the + operator is usually determined by the left operand:

System.out.println(3 + 'a'); //100

However, Strings are an exception. If either operand is a String, the result is expected to be a String. This is the reason null is converted into "null", even though you might expect a RuntimeException.

Zarathustra answered 30/12, 2021 at 19:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.