Analysis and comparison of StringBuilder and StringBuffer

Source code analysis of StringBuilder and StringBuffer

StringBuilder source code analysis

Class structure

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

StringBuilder is decorated with the final keyword, which can't be inherited like String

StringBuilder inherits AbstractStringBuilder and implements Serializable and CharSequence, which can be serialized

Method

StringBuilder's methods mostly call the methods of the parent class AbstractStringBuilder directly. Here are a few typical methods

StringBuilder append(Object obj) method overrides the method of the parent class and appends the element of Object type

@Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));//String.valueOf(obj) gets the string converted from the object
    }
public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }
@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();//Append string 'null' if NULL
        int len = str.length();
        ensureCapacityInternal(count + len);
  //Copy string to array
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

StringBuilder delete(int start, int end) delete the characters from the specified start subscript to the specified end subscript

@Override
    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)//If the ending subscript > the maximum subscript of the currently saved char, directly assign it as the maximum subscript
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
          //Copy the element after deleting the ending subscript to the element after deleting the starting subscript
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

StringBuilder replace(int start, int end, String str)

@Override
    public StringBuilder replace(int start, int end, String str) {
        super.replace(start, end, str);
        return this;
    }
 public AbstractStringBuilder replace(int start, int end, String str) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (start > count)
            throw new StringIndexOutOfBoundsException("start > length()");
        if (start > end)
            throw new StringIndexOutOfBoundsException("start > end");

        if (end > count)
            end = count;
        int len = str.length();
   //Calculate required capacity
        int newCount = count + len - (end - start);
   //Capacity expansion
        ensureCapacityInternal(newCount);
	//Delete characters in the specified range
        System.arraycopy(value, end, value, start + len, count - end);
   //Inserts a string at the beginning of the deletion
        str.getChars(value, start);
        count = newCount;
        return this;
    }

StringBuilder insert(int offset, Object obj) inserts an object at a specified location

@Override
    public StringBuilder insert(int offset, Object obj) {
            super.insert(offset, obj);
            return this;
    }
public AbstractStringBuilder insert(int offset, Object obj) {
        return insert(offset, String.valueOf(obj));//String.valueOf(obj) gets the string of object conversion
    }
public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }
 @Override
    public StringBuilder insert(int offset, String str) {
        super.insert(offset, str);
        return this;
    }
public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
  	//Capacity expansion
        ensureCapacityInternal(count + len);
  //Move a certain number of characters (length of inserted string) after the position to be inserted back a certain distance (length of inserted string)
        System.arraycopy(value, offset, value, offset + len, count - offset);
  	//Insert string to insert
        str.getChars(value, offset);
        count += len;
        return this;
    }

As you can see, the append, insert, replace and delete of StringBuilder are all operations on the char array of the parent class, and no new objects are generated

The most quintessential method of String toString()

@Override
    public String toString() {
        //String the final char array modified by some columns
        return new String(value, 0, count);
    }

Here we see that in toString, the char array is generated into a String, which is the reason why StringBuilder is more efficient than String. If the String class is not modified at all, it will generate new objects. Therefore, when strings are frequently concatenated and intercepted, the efficiency is certainly not as good as StringBuilder

StringBuffer source code analysis

Class structure

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

The class structure of StringBuffer is the same as that of StringBuilder

Method

StringBuffer is the same as StringBuilder. Many methods call the methods of the parent class AbstractStringBuilder. Let's look at the main methods

StringBuffer append(Object obj) appends objects to StringBuffer. The code is the same as that of StringBuilder

@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
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;
    }
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);
    }

StringBuffer delete(int start, int end) deletes characters within the specified range, just like the delete method code in StringBuilder

@Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;
        super.delete(start, end);
        return this;
    }
public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

The StringBuffer replace(int start, int end, String str) method uses strings to replace characters within the specified range, just like the replacement method code of StringBuilder

@Override
    public synchronized StringBuffer replace(int start, int end, String str) {
        toStringCache = null;
        super.replace(start, end, str);
        return this;
    }
public AbstractStringBuilder replace(int start, int end, String str) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (start > count)
            throw new StringIndexOutOfBoundsException("start > length()");
        if (start > end)
            throw new StringIndexOutOfBoundsException("start > end");

        if (end > count)
            end = count;
        int len = str.length();
        int newCount = count + len - (end - start);
        ensureCapacityInternal(newCount);

        System.arraycopy(value, end, value, start + len, count - end);
        str.getChars(value, start);
        count = newCount;
        return this;
    }

StringBuffer insert(int offset, Object obj) inserts a string at a specified location, which is the same as the insert method code of StringBuilder

 @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
        toStringCache = null;
        super.insert(offset, String.valueOf(obj));
        return this;
    }
public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }

By analyzing the source code of these methods, we can see that StringBuilder and StringBuffer are consistent in the implementation of the methods. The only difference is that all methods of StringBuffer have synchronized locks, so they are thread safe

String toString() converts StringBuffer to string

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

StringBuffer and StringBuilder do not generate new objects when they are modified. Only when toString method is called can they be converted to strings.

summary

  1. The class structure of StringBuilder and StringBuffer is the same. Both of them use the char array of the parent class to save characters.
  2. All methods of StringBuffer have synchronized lock, so it is thread safe, but it also makes its efficiency lower than StringBuilder.
  3. The basic idea of StringBuilder and StringBuffer is the same. Any modification to StringBuilder and StringBuffer will not generate new objects, which also makes StringBuilder and StringBuffer more efficient than String when they perform a large number of String concatenation interceptions.

Tags: Java

Posted on Sat, 02 May 2020 09:04:05 -0700 by matbennett