Talk about StringBuffer and StringBuilder

As we know, String is a read-only String. Once the referenced String is defined, it cannot be modified.
Splicing or intercepting a String will create a new String object. If you need to make a lot of changes to the String, the performance of using String is extremely low.

As an example, String is spliced 100000 times. The performance of String is about 500 times slower than that of StringBuilder.

public static void main(String[] args) {
	String s = "";
	long t1 = System.currentTimeMillis();
	for (int i = 0; i < 100000; i++) {
		s += "0";
	}
	long t2 = System.currentTimeMillis();
	System.out.println(t2 - t1);
	//Time: 5106ms
}

public static void main(String[] args) {
	StringBuilder sb = new StringBuilder();
	long t1 = System.currentTimeMillis();
	for (int i = 0; i < 100000; i++) {
		sb.append("0");
	}
	long t2 = System.currentTimeMillis();
	System.out.println(t2 - t1);
	//Time consuming: 9ms
}

Therefore, if the String needs frequent modification, do not use String. Instead, use StringBuilder or StringBuffer.

The difference between StringBuilder and StringBuffer

Thread safety

The biggest difference is thread safety.

StringBuffer is thread safe, while StringBuilder is thread unsafe.
This means that using StringBuilder under multithreading will result in unforeseen exceptions and errors.

It will be described in detail later, unsafe StringBuilder.

performance

StringBuffer is thread safe, which means low performance.
The exposed methods of StringBuffer are added with the synchronized keyword, which means that each modification of string needs to compete for lock and release lock. Although JDK has made a lot of optimization on lock mechanism, it still needs to consume extra performance.

In the following example, string is spliced 10 million times. The performance of StringBuilder is three times that of StringBuffer.

public static void main(String[] args) {
	StringBuilder sb = new StringBuilder();
	long t1 = System.currentTimeMillis();
	for (int i = 0; i < 10000000; i++) {
		sb.append("0");
	}
	long t2 = System.currentTimeMillis();
	System.out.println(t2 - t1);
	//Time: 121ms
}

public static void main(String[] args) {
		StringBuffer sb = new StringBuffer();
	long t1 = System.currentTimeMillis();
	for (int i = 0; i < 10000000; i++) {
		sb.append("0");
	}
	long t2 = System.currentTimeMillis();
	System.out.println(t2 - t1);
	//Time: 361ms
}

Therefore, if you do not need to consider thread safety, you can consider using StringBuilder to improve performance.

cache

StringBuffer supports caching, but StringBuilder does not.

When StringBuffer is in toString, it will cache the result of the character array into the property toStringCache. As long as the content of the string does not change, each time toString will directly use toStringCache to build Stirng instead of copying the character array.
While StringBuilder does not have a cache. Every time toString copies a new character array to build a String.

StringBuffer as long as the string content is modified, toStringCache will be set to null.

toString() method of StringBuffer

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

toString() method of StringBuilder

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

Insecure StringBuilder

As mentioned above, StringBuilder is not thread safe, and unpredictable exceptions and errors will occur when it is used under multithreading.
So what's going to happen?

Inconsistent expected results

When StringBuilder is operated under multithreading, the final result of string may be inconsistent with the expectation.

In the following example, the content of the string is inconsistent with the expectation:

public static void main(String[] args) throws InterruptedException {
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < 10; i++) {
		new Thread(()->{
			try {
				//After Sleep, the results were more obvious
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sb.append("1");
		}).start();
	}
	Thread.sleep(1000);
	System.out.println("Expected results:1111111111");
	System.out.println("Actual result:"+sb.toString());
}

The console output is as follows:

Expected result: 1111111111
 Actual result: 111111

ArrayIndexOutOfBoundsException

Multithreading calls the append() method of StringBuilder. When copying arrays internally, an ArrayIndexOutOfBoundsException may be thrown.

Here is an example:

public static void main(String[] args) throws InterruptedException {
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < 10; i++) {
		new Thread(()->{
			try {
				//After Sleep, the results were more obvious
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//The longer the append string is, the easier to throw an exception
			sb.append("000000000000000000000000000000000000000000000000000");
		}).start();
	}
}

Console exception:

append() method parsing

The append() of StringBuilder directly calls the method of the parent class AbstractStringBuilder, so just look at the parent class directly.

The append method is not locked, which means that multiple threads can call concurrently.
Because it is a concurrent call, the previous thread has not finished executing count += len code, and the later thread has expanded the capacity, resulting in the wrong expansion of the thread. The character array has not been expanded to the correct size, resulting in the exception of ArrayIndexOutOfBoundsException thrown during str.getChars() operation.

Of course, exceptions are not always thrown.
This is because the default size of StringBuilder character array is 16, and the expansion logic is the current size * 2 + 2. If the append character length is relatively short, even if the character array is expanded incorrectly, the capacity is enough, and the exception will not be thrown.
However, even if the exception is not thrown, because the count attribute under multithreading is chaotic, it will cause the wrong copy of character array, that is, the previous append string will be overwritten, resulting in the content of the string inconsistent with the expected.

So, if you are in a multithreaded environment, remember to use StringBuffer, because its exposed methods are all added with the synchronized keyword, which can ensure the security of concurrency.

Published 100 original articles, won praise 23, visited 90000+
Private letter follow

Tags: JDK Attribute

Posted on Sun, 19 Jan 2020 09:42:23 -0500 by gth759k