Java StringBuilder and StringBuffer source code analysis

  • 2020-04-01 04:35:49
  • OfStack

StringBuilder and StringBuffer are two commonly used classes that manipulate strings. As you all know, StringBuilder is thread-unsafe, and StringBuffer is thread-safe. The former is added in JDK1.5 and the latter is available in JDK1.0. Let's examine their internal implementation.

1. Inheritance


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

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

As you can see, the inheritance relationship between the two classes is exactly the same. Serializable is a flag that can be serialized. CharSequence interface contains the charAt (), length (), subSequence (), toString () these methods, the String class also implements this interface. The focus here is on the abstract class AbstractStringBuilder, which encapsulates the implementation of StringBuilder and most operations of StringBuffer.

Second, the AbstractStringBuilder

1. Variables and construction methods


char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
  value = new char[capacity];
}
  

The AbstractStringBuilder holds strings internally with a char[] array, and you can specify the initial capacity method at construction time.

2,


public void ensureCapacity(int minimumCapacity) {
  if (minimumCapacity > 0)
    ensureCapacityInternal(minimumCapacity);
}
 private void ensureCapacityInternal(int minimumCapacity) {
  // overflow-conscious code
  if (minimumCapacity - value.length > 0)
    expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
  int newCapacity = value.length * 2 + 2;
  if (newCapacity - minimumCapacity < 0)
    newCapacity = minimumCapacity;
  if (newCapacity < 0) {
    if (minimumCapacity < 0) // overflow
      throw new OutOfMemoryError();
    newCapacity = Integer.MAX_VALUE;
  }
  value = Arrays.copyOf(value, newCapacity);
}

The capacity expansion method is ultimately implemented by expandCapacity(), in which the capacity is first expanded to the original capacity plus 2, and if it is still smaller than the specified capacity, the new capacity is set to minimumCapacity. Then determine if there is an overflow, and if so, set the capacity to integer.max_value. Finally, copy the value, which is obviously a time-consuming operation.

3. 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;
  }

Append () is the most common method and has many forms of overloading. Above is one of them, used to append strings. If STR is null, the appendNull() method is called. This method actually appends 'n', 'u', 'l', 'l'. If it is not null, first expand, then call the String's getChars() method to append STR to the end of value. The object itself is returned, so append() can be called consecutively.

Third, the StringBuilder

AbstractStringBuilder already implements most of the required methods; StringBuilder and StringBuffer only need to be called. Let's look at the implementation of StringBuilder.

1. Constructors


public StringBuilder() {
  super(16);
}
public StringBuilder(int capacity) {
  super(capacity);
}
public StringBuilder(String str) {
  super(str.length() + 16);
  append(str);
}
public StringBuilder(CharSequence seq) {
  this(seq.length() + 16);
  append(seq);
}

As you can see, StringBuilder has a default capacity size of 16. You can also specify the initial capacity or assign an initial value to a StringBuilder object with an existing sequence of characters.

2. Append () method


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

Append () has many overloaded methods, but I've listed two of them here. Obviously, this is a direct call to a method in the parent AbstractStringBuilder.

3, the toString ()


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

The toString() method returns a new String object that does not share memory with the original object. The same is true of the subString() method in AbstractStringBuilder.

Four, SringBuffer

StiringBuffer is similar to StringBuilder, except that many methods use lSynchronized for synchronization purposes, as follows:


public synchronized int length() {
    return count;
}
public synchronized StringBuffer append(String str) {
  toStringCache = null;
  super.append(str);
  return this;
}
public synchronized void setLength(int newLength) {
  toStringCache = null;
  super.setLength(newLength);
}

As you can see, the method does have Synchronized before it.
In addition, there is a variable toStringCache in the append() and setLength() methods above. This variable is used as the last cache for the toString() method, and is assigned to null whenever the StringBuffer is modified. The toString of StringBuffer is as follows:


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

In this method, if the toStringCache is null, cache first. The String object that ends up being returned is a little different, and this constructor also takes the parameter true. Find the source String look:


 String(char[] value, boolean share) {
  // assert share : "unshared not supported";
  this.value = value;
}

It turns out that the String object constructed by this constructor doesn't actually copy the String, it just points the value to the construct parameter, to save time on copying the element. However, this constructor has package access and cannot be called in general.

conclusion

Both StringBuilder and StringBuffer are mutable strings, the former thread-unsafe and the latter thread-safe. Most methods of StringBuilder and StringBuffer invoke an implementation of the parent AbstractStringBuilder. The expansion mechanism is to change the capacity to the original capacity of 2 times 2. The maximum capacity is integer.max_value, which is 0x7fffffff. Both StringBuilder and StringBuffer have a default capacity of 16, so it is best to estimate the size of the string in advance to avoid the time consumption caused by expansion.

That's all for this article, which I hope will help you learn about the two Java classes StringBuilder and StringBuffer, which commonly manipulate strings.


Related articles: