Why using + to concatenate string in Java loop is not a good option

  sonic0002        2019-01-18 22:07:12       10,915        0    

String concatenation is a common operation in Java programming. It is to concatenate multiple strings into a single string. Java provides a String class which is an immutable class which means the object cannot be mutated once instantiated.

Once a String object is instantiated, its properties cannot be changed anymore, so when concatenating strings, it's actually create a new String instance to store the concatenated string values. For example, below is a simple string concatenation example.

String s = "abcd";
s = s.concat("ef");

When this piece of code is executed, a new String instance is created and assigned to s

In Java, there are many ways to concatenate strings. Below are some of them.

Using + operator

In Java, using + operator to concatenate strings is the most commonly used way. It's very simple to use.

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;

One thing to note here is that many people consider + is an operator overloading, indeed it's not, Java doesn't support operator overloading. This is actually a syntactic sugar.

Operator overloading is a type of polymorphism, what it does is to redefine an operator so that it can have different functions to adapt to different data types. While syntactic sugar is a kind of syntax added to the  language to make it more convenient for programmer to use, it doesn't introduce any new functionality to the language itself.

How does + work in Java? Let's decompile the code above and see what it looks like.

String wechat = "Hollis";

String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章

String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();

From the decompiled code, it's easy to find out that the string is converted to an StringBuilder object and then use append method to concatenate the string. This means + internally uses StringBuilder.append.

Using concat

In addition to +, concat method in String class can also be used to concatenate strings.

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat.concat(",").concat(introduce);

If we take a look at the source code of concat method, we will see

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

It first creates a char array with length to be the sum of existing string length and to be concatenated string length, then it copies the original string and the to be concatenated string into the new char array and return a new String object. 

Using StringBuilder

There is also another class called StringBuilder.

StringBuilder wechat = new StringBuilder("Hollis");
String introduce = "每日更新Java相关技术文章";
StringBuilder hollis = wechat.append(",").append(introduce);

From the source code, StringBuilder also creates a char array, in contrast to String, the char array is not final and it can be modified. Also, there is a count variable in this class to record how many spaces in char array has been occupied. The append method source code is:

public StringBuilder append(String str) {
 super.append(str);
 return this;
}

This class extends AbstractStringBuilder class, its append method

public AbstractStringBuilder append(String str) {
 if (str == null)
 return appendNull();

 int len = str.length();
 ensureCapacityInternal(count + len);
 str.getChars(0, len, value, count);
 count += len;
 return this;
}

If the char array doesn't have enough space for the new concatenated string, it will extend.

Using StringBuffer

Apart from String class which can be used to create string literal, Java also provides StringBuffer class to handle strings. The object can be mutated. It's also easy to concatenate strings with StringBuffer.

StringBuffer wechat = new StringBuffer("Hollis");
String introduce = "每日更新Java相关技术文章";
StringBuffer hollis = wechat.append(",").append(introduce);

Different from StringBuilder, StringBuffer is thread-safe. Take a look at its append method.

public synchronized StringBuffer append(String str) {
 toStringCache = null;
 super.append(str);
 return this;
}

Using StringUtils.join

Besides the built-in classes and methods provided by JDK, some open source libraries also provide convenient ways to concatenate strings. One of them is the StringUtils.join provided by apache.commons

String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
System.out.println(StringUtils.join(wechat, ",", introduce));

In Java 8, the JDK also provides a join() method for similar purpose.

The source code for above method looks like.

public static String join(final double[] array, final char separator, final int startIndex, final int endIndex) {
 if (array == null) {
   return null;
 }

 final int noOfItems = endIndex - startIndex;
 if (noOfItems <= 0) {
   return EMPTY;
 }

 final StringBuilder buf = new StringBuilder(noOfItems * 16);
 for (int i = startIndex; i < endIndex; i++) {
   if (i > startIndex) {
     buf.append(separator);
   }
   buf.append(array[i]);
 }
 return buf.toString();
}

Comparisons

Which method has better efficiency? Let's test it out.

long t1 = System.currentTimeMillis();


for (int i = 0; i < 50000; i++) {
 // string concatenation code
}

long t2 = System.currentTimeMillis();
System.out.println("cost:" + (t2 - t1));

The results are:

+ cost:5119
StringBuilder cost:3
StringBuffer cost:4
concat cost:3623
StringUtils.join cost:25726

From the result, we have below findings.

  • StringBuffer is built based on StringBuilder but with synchronization, so it takes more time
  • StringUtils.join also uses StringBuilder and has some other operations, so it also takes more time
  • + also uses StringBuilder(based on decompiled code), but it takes much more time

Why would + take more time? Assume we have below code

long t1 = System.currentTimeMillis();
String str = "hollis";

for (int i = 0; i < 50000; i++) {
 String s = String.valueOf(i);
 str += s;
}

long t2 = System.currentTimeMillis();
System.out.println("+ cost:" + (t2 - t1));

The decompiled code looks like

long t1 = System.currentTimeMillis();
String str = "hollis";

for(int i = 0; i < 50000; i++)
{
 String s = String.valueOf(i);
 str = (new StringBuilder()).append(str).append(s).toString();
}

long t2 = System.currentTimeMillis();
System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

In the for loop, a new StringBuilder instance is created everytime before appending the string. Creating new instance takes time and also consumes memory. This is the reason why + would take much longer than normal StringBuilder.

So the most important takeaway here is that avoiding to use + in loops to concatenate strings in Java. Instead StringBuilder or StringBuffer should be used if possible.

Reference: https://www.toutiao.com/i6646214111879234051

JAVA  STRING  JAVA 8 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

I love GDPR