This article is an original article of Joshua 317. Please indicate: reprinted from Joshua 317 blog https://www.joshua317.com/article/241
1, What is the difference between String, StringBuilder and StringBuffer?
String is a very basic and important class of Java language. It provides various basic logic for constructing and managing strings. String is a read-only string. It is not a basic data type, but an object. From the perspective of the underlying source code, it is a character array of final type. The referenced string cannot be changed. Once defined, it cannot be added, deleted or modified. Due to its immutability, such actions as splicing and cutting strings will generate new string objects every time you operate on a string
private final char value[];
StringBuffer is a class provided to solve the problem that too many intermediate objects are generated by splicing mentioned above. You can use the append or add method to add the string to the end of the existing sequence or the specified position. StringBuffer is essentially a thread safe and modifiable character sequence. It ensures thread safety and brings additional performance overhead. Therefore, unless there is a need for thread safety, its successor, StringBuilder, is recommended.
StringBuilder is newly added in Java 1.5. It has no essential difference from StringBuffer in capability, but it removes the thread safety part and reduces the overhead. It is the first choice for string splicing in most cases.
StringBuffer and StringBuilder both inherit the AbstractStringBuilder abstract class. We can see from the AbstractStringBuilder abstract class
/** * The value is used for character storage. */ char[] value;
The internal implementation of StringBuilder and StringBuffer, like the String class, stores strings through a char array. The difference is that the char array in the String class is final modified and immutable, while the char array of StringBuilder and StringBuffer is variable. Therefore, it is recommended to use StringBuffer and StringBuilder for frequent String operations. In addition, StringBuffer adds a synchronization lock to the method or adds a synchronization lock to the called method, so it is thread safe. StringBuilder does not apply synchronization lock to methods, so it is non thread safe.
When we need to make a lot of modifications to the string, we recommend using StringBuffer and StringBuilder classes.
Unlike the String class, the objects of the StringBuffer and StringBuilder classes can be modified repeatedly without leaving a large number of new unused objects.
The StringBuilder class was introduced from Java 5. The main difference between StringBuffer and StringBuilder is that the StringBuilders method is not thread safe (asynchronous).
It is recommended to use the StringBuilder class whenever possible because it is faster than StringBuffer. However, if thread safety is required, it is best to use the StringBuffer class.
2, Code programming shows that StringBuilder is not thread safe
Next, we use StringBuilder and StringBuffer to append strings. Let's see the difference
Let's do a simple test
First, create 10 threads; Then, each thread loops 100 times StringBuilder perhaps StringBuffer Inside the object append Character. We expect the output to be 1000, but what will be output in actual operation?
2.1 StringBuilder test
package com.joshua317; public class StringBuilderTest { public static void main(String[] args) throws InterruptedException { StringBuilder stringBuilder = new StringBuilder(); for (int i=0; i<10; i++) { (new Thread(new ThreadTestStringBuilder(stringBuilder))).start(); } Thread.sleep(100); System.out.println(stringBuilder.length()); } } class ThreadTestStringBuilder implements Runnable { public StringBuilder stringBuilder; ThreadTestStringBuilder(StringBuilder stringBuilder) { this.stringBuilder = stringBuilder; } @Override public void run() { for (int j=0; j < 100; j++) { stringBuilder.append("a"); } } }
2.2 StringBuffer test
package com.joshua317; import java.util.Collection; public class StringBufferTest { public static void main(String[] args) throws InterruptedException { StringBuffer stringBuffer = new StringBuffer(); for (int i=0; i<10; i++) { (new Thread(new ThreadTestStringBuffer(stringBuffer))).start(); } Thread.sleep(100); System.out.println(stringBuffer.length()); } } class ThreadTestStringBuffer implements Runnable { public StringBuffer stringBuffer; ThreadTestStringBuffer(StringBuffer stringBuffer) { this.stringBuffer = stringBuffer; } @Override public void run() { for (int j=0; j<1000; j++) { stringBuffer.append("a"); } } }
Through the above two examples, we find that during multithreading execution of StringBuilder, the character length may be less than 1000, and even the ArrayIndexOutOfBoundsException exception (exception is not necessary). The normal output string length of StringBuffer is 1000, so we can simply conclude that StringBuilder is non thread safe
3, Analyze the problems in the execution of StringBuilder
3.1 why is the output value different from the expected value
Let's first look at the two member variables of StringBuilder (these two member variables are actually defined in AbstractStringBuilder, and both StringBuilder and StringBuffer inherit AbstractStringBuilder)
/** * The value is used for character storage. * For character storage */ char[] value; /** * The count is the number of characters used. * Number of characters used to record */ int count;
When instantiating StringBuilder again, we can see that the default capacity of StringBuilder is 16. Of course, you can also specify the initial capacity, or assign an initial value to the StringBuilder object with an existing character sequence.
StringBuilder stringBuilder = new StringBuilder(); //StringBuilder class public StringBuilder() { super(16); } //AbstractStringBuilder class AbstractStringBuilder(int capacity) { value = new char[capacity]; }
Look at the append method of StringBuilder
@Override public StringBuilder append(String str) { super.append(str); return this; }
Then, the append method of StringBuilder calls the append method of the parent class AbstractStringBuilder.
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; }
Slave code count += len; We can see that it is not an atomic operation. Suppose that the count value is 100 and the len value is 1 at this time. When the two threads execute this line at the same time, the count value obtained is 101. After performing the addition operation, the result is assigned to count, so the count value after the two threads execute is 101 instead of 102. This leads to the reason that the count value of characters is smaller than our expected result of 1000.
Let's take another look at the append operation of StringBuffer
@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
The append method of StringBuffer is modified by synchronized to ensure synchronous operation in the case of multithreading.
3.2 why is an ArrayIndexOutOfBoundsException thrown
Let's take a look at the ensureCapacityInternal() method in the append() method of AbstractStringBuilder. It is mainly used to check whether the capacity of the original char array of StringBuilder object can accommodate the new string. If not, call the ensureCapacityInternal() method to expand the capacity of the char array, That is to say, the ensurecapacityinternal () method in StringBuilder is used to extend the value array.
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; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; } //java.lang.Integer @Native public static final int MAX_VALUE = 0x7fffffff;
//java. util. In the arrays class public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } //java.lang.System class public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
The capacity expansion method is finally implemented by expandCapacity(). In this method, first change the capacity to twice the original capacity plus 2. If it is still less than the specified capacity at this time, set the new capacity to minimumCapacity. Then judge whether it overflows. If it overflows, set the capacity to MAX_ARRAY_SIZE. Finally, through arrays The copyof() method calls system The arraycopy () method copies the value value.
Then continue to look at the code. str.getChars(0, len, value, count) in the append() method of AbstractStringBuilder class;
This line is used to copy the contents of the char array in the String object to the char array of the StringBuilder object
str.getChars(0, len, value, count);
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }
We use a flowchart to represent the process of string copying
1. Suppose that two threads have executed the append() method of StringBuilder at the same time, and both threads have completed the ensureCapacityInternal() method of AbstractStringBuilder. At this moment, count=101.
2. At this time, the cpu time slice of thread 1 runs out, and thread 2 continues to execute. After thread 2 executes the complete append() method, the count becomes 102.
3. When thread 1 continues to execute the str.getChars() method of AbstractStringBuilder, the count value obtained is 102. When copying the char array, an ArrayIndexOutOfBoundsException exception will be thrown.
So far, the problems in the execution of StringBuilder have been analyzed, and the problem of unsafe multithreading of StringBuilder has been solved.
4, Expand
4.1 what is thread safety?
When multiple threads access a class (object or method), the public data area corresponding to the object can always behave correctly, then the class (object or method) is thread safe. Generally speaking, if there are multiple threads running in the process of your code, these threads may run this code at the same time. If the result of each run is the same as that of single thread, and the values of other variables are the same as expected, it is thread safe.
4.2 how to use String, StringBuffer and StringBuilder?
String | StringBuffer | StringBuilder |
---|---|---|
Immutable string | Variable character sequence | Variable character sequence |
Low efficiency | efficient | |
Thread safety | Thread unsafe |
So,
- If you want to manipulate a small amount of data, use String;
- Multithreading operates a large amount of data in the string buffer, using StringBuffer;
- A single thread operates a large amount of data in the string buffer, and uses StringBuilder.
Remember, StringBuffer is applicable to the scenario where multiple threads operate the same StringBuffer, and StringBuilder is more suitable for single thread.
4.3 specific application scenarios
-
In business scenarios where the String content does not change frequently, the String class is preferred. For example: constant declaration, a small number of String splicing operations, etc.
-
It is recommended to perform string parsing and replacement in the environment of multiple threads, such as parsing and replacing XML parameters (for example, using HTTP buffer).
-
When string operations are frequently performed (such as splicing, replacement, deletion, etc.) and run in a single threaded environment, it is recommended to use StringBuilder, such as SQL statement assembly, JSON encapsulation, etc.
4.4 why is StringBuilder thread unsafe?
Because compared with StringBuffer, StringBuilder does not use the synchronized keyword on the method. The synchronized keyword solves the synchronization of accessing resources between multiple threads. The synchronized keyword can ensure that only one thread can execute the modified method or code block at any time. For how to analyze, please refer to the above.
This article is an original article of Joshua 317. Please indicate: reprinted from Joshua 317 blog https://www.joshua317.com/article/241