StringBuffer StringBuilder 源码分析

StringBuffer 和 StringBuilder

StringBuffer 和 StringBuilder 与 String 不同,String的底层value数组以及String类都被final修饰,因此String新建后是不可修改并且不可被继承的。StringBuilder与StringBuffer是两个常用的字符串操作类,它们底层的value数组没有被final修饰(但是他们的类被final修饰,所以依然不能被继承)。下面通过源码分析StringBuffer StringBuilder的底层实现以及两者关系。

string.png

AbstractStringBuilder

通过上面的UML图可以知道StringBuffer和StringBuilder都继承了AbstractStringBuilder抽象类。由于AbstractStringBuilder实现了大部分功能,下面对AbstractStringBuilder进行分析。

1
abstract class AbstractStringBuilder implements Appendable, CharSequence

通过类的定义知道了 AbstractStringBuilder 实现了 Appendable, CharSequence 两个接口,CharSequence接口在 String 源码分析 中分析过。Appendable接口主要是定义了append方法,具体实现我们在具体类中分析。下面看 AbstractStringBuilder 的成员变量。

1
2
3
char[] value;

int count;

通过代码知道 AbstractStringBuilder 底层的value数组是没有被final修饰的,这也说明了 StringBuffer 和 StringBuilder 可以对字符串进行操作。

AbstractStringBuilder 的扩容

由于 append 方法会涉及 AbstractStringBuilder 的扩容,我们先了解他的扩容机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public void ensureCapacity(int minimumCapacity) {
// 如果需要最小容量大于零,进行扩容
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}

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
// 新容量为旧容量的2倍再加2
int newCapacity = (value.length << 1) + 2;
// 如果新容量比需要最小容量还小,则直接扩容至minCapacity
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 如果新容量大于 MAX_ARRAY_SIZE,还要做进一步判断
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
// 否则直接返回新容量
: newCapacity;
}

private int hugeCapacity(int minCapacity) {
// 如果需要的容量比 Integer.MAX_VALUE 还大,抛出异常
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
// 在 minCapacity 小于 Integer.MAX_VALUE的情况下,返回 minCapacity 和 MAX_ARRAY_SIZE 中的大值
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}

以上就是它的扩容机制,仔细回忆和ArrayList的扩容机制是有很多相似之处的。值得一提的是,在扩容过程中minCapacity会与MAX_ARRAY_SIZE进行比较,在ArrayList中也可以看到类似的地方,通过类变量声明可以看到MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;为什么是比Integer.MAX_VALUE小8呢?这里涉及JVM的知识,是因为数组作为一个对象,需要一定内存存储对象头信息,对象头信息最大占用内存不可超过8字节。如果数组设的过大,会导致堆区内存不足或者超过了JVM的最大限制,则有OutOfMemoryError错误。

append方法

分析完扩容机制后,我们可以看看它的append方法的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public AbstractStringBuilder append(String str) {
// 如果str为空,调用 appendNull 方法
if (str == null)
return appendNull();
// 得到str的长度len
int len = str.length();
// 对新容量进行扩容
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count); // 调用 String 的 getChars() 方法将 str 追加到 value 末尾
count += len; // 更新 AbstractStringBuilder 的 count 属性
return this;
}


private AbstractStringBuilder appendNull() {
// 如果 str 为空 会进入这个方法 这个方法会在当前字符串后追加"null"
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}

StringBuilder

分析完 AbstractStringBuilder 后,下面开始分析StringBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

StringBuilder 的构造方法基本是调用了父类 AbstractStringBuilder 的构造方法,并且默认的初始化容量为16。

append方法

1
2
3
4
5
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

StringBuilder的append方法也是调用了父类的append方法。

toString方法

1
2
3
4
5
6
 @Override
public String toString() {
// Create a copy, don't share the array
// toString方法新建了一个String对象调用了 public String(char value[], int offset, int count) 构造方法(即带有偏移量与个数的构造方法)
return new String(value, 0, count);
}

StringBuffer

append方法

1
2
3
4
5
6
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

StringBuffer的append方法与StringBuilder基本一致,但是这个方法是被Synchronized关键字修饰,即他是同步的,并且可以看到方法中有个toStringCache变量,这个变量是最近一次toString()方法的缓存,任何写操作都会将该缓存重设为null。

toString方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public synchronized String toString() {
// 如果 toStringCache 为空,toStringCache会存入目前的value作为缓存
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
// 新建一个String对象,通过下面的构造方法可以知道是直接将String的value数组直接指向了缓存
return new String(toStringCache, true);
}

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

总结

  • StringBuffer 和 StringBuilder 和 String 一样都被final修饰,他们都是不可继承的,但是和String不同的是,它们底层的value数组没有被final关键字修饰,因此他们是可操作的,平时也是使用它们来对字符串进行操作
  • AbstractStringBuilder 的扩容机制与ArrayList类似,但是它的扩容是原容量的两倍加二
  • StringBuffer与StringBuilder不同的是,它通过Synchronized关键字进行了同步,因此它是线程安全的