前言

StringBuilderStringBuffer是两个常用的字符串操作类,与String的不同之处在于他们是可变的,不像Stringvalue数组被final修饰的严严实实的,而StringBuilderStringBuffervalue数组没有被final修饰过,并且这两个类的实现几乎一样,主要的区别在于StringBuffer的方法由synchronized关键字修饰过,所以是线程安全的。这里先贴出整个体系的UML类图:

AbstractStringBuilder

从上面的类图也能看到,StringBuilderStringBuffer均继承自抽象类AbstractStringBuilderAbstractStringBuilder为子类提供了大部分的实现,因此,我们有必要先分析一下AbstractStringBuilder的源码。

成员变量

AbstractStringBuilder主要有以下两个成员变量,值得注意的是value并没有被final修饰,意味着它是可变的。

1
2
3
4
// 与 String 一样维护一个字符数组
char[] value;
// 字符的个数
int count;

在这里顺便将构造函数的源码也分析了:

1
2
3
4
5
6
AbstractStringBuilder() {
}

AbstractStringBuilder(int capacity) {
value = new char[capacity]; // 初始化为指定容量
}

扩容

在分析关键的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
27
28
29
30
31
32
33
34
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
int newCapacity = (value.length << 1) + 2; // 计算新容量为原来容量的两倍加2
if (newCapacity - minCapacity < 0) { // 如果计算出的新容量不够大
newCapacity = minCapacity; // 直接将新容量设为所需容量
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity) // 如果新容量大于 MAX_ARRAY_SIZE,还要做进一步判断
: newCapacity; // 否则,返回新容量即可
}

private int hugeCapacity(int minCapacity) {
// 如果所需容量大于整型最大值,则直接抛出 OutOfMemoryError
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
// 如果所需容量大于 MAX_ARRAY_SIZE 且小于整型最大值时,返回所需容量
// 如果所需容量小于 MAX_ARRAY_SIZE,返回 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}

上面的扩容操作逻辑有点复杂,这里先总结下流程:

  1. 默认的新容量大小为原容量大小的两倍加2,如果还不够,就直接设为所需要的容量大小
  2. 如果新容量大小比MAX_ARRAY_SIZE小,那么直接返回该新容量大小
  3. 否则,检查需要的容量大小是否超过整型最大值,如果超过则抛出异常
  4. 如果需要的容量大小比MAX_ARRAY_SIZE大,则直接返回需要的容量大小;否则,返回MAX_ARRAY_SIZE

其实这个扩容操作和ArrayList的扩容操作逻辑基本一致,这里的MAX_ARRAY_SIZE的值为Integer.MAX_VALUE - 8也就是整型的最大值减8,那么为什么要设置成这个值呢?其实在注释中也有说明,一些虚拟机的实现可能会在数组中存储header words,因此如果分配比这个值更大的容量的话,有可能会导致OutOfMemoryError

append()

接下来看看append()方法,append()方法是整个类的核心,我们在实际中也经常使用。实际上它有很多个重载方法,这里就只分析参数为String类型的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public AbstractStringBuilder append(String str) {
if (str == null) // 如果参数为 null
return appendNull(); // 实际上是添加 'n' 、'u'、'l' 、'l' 四个字符到 value 数组中
int len = str.length();
ensureCapacityInternal(count + len); // 扩容
str.getChars(0, len, value, count); // 调用 String 的 getChars() 方法将 str 追加到 value 末尾
count += len; // 更新字符长度
return this; // 返回自身,支持链式调用
}

private AbstractStringBuilder appendNull() {
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;
}

可以看到append()方法其实逻辑挺简单的,但有两点是需要注意到的,一个是追加字符串的操作是通过StringgetChars()完成的,但最后还是调用的System.arraycopy()这个native方法;另一个是该方法返回的是自身this,通过这种方式,我们可以实现append()方法的链式调用。

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有四个重载的构造函数,并且默认的初始化容量为16,当然我们也可以指定初始化容量,或者直接传入一个已有的字符序列。

append()

StringBuilderappend()方法有非常多的重载,但其实都是调用父类AbstractStringBuilder的方法,在上面已经分析过了,所以这里就简单看一个:

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

StringBuffer

StringBufferStringBuilder的实现基本一样,只不过方法被synchronized关键字修饰了,因此是线程安全的,比如下面的append()方法:

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

可以看到方法中有个toStringCache变量,这个变量是最近一次toString()方法的缓存,任何写操作都会将该缓存重设为null,我们看下这个toString()方法:

1
2
3
4
5
6
7
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}

可以看到,如果缓存为空的话,那么就会先填充缓存,否则直接使用缓存new一个新的String对象并返回,但要注意的是,这里并不会有复制操作,而是直接将String对象中的value指向这个缓存数组:

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

总结

  • String不同,StringBuilderStringBuffer都是可变字符串,底层value数组没有使用final关键字修饰
  • StringBuilderStringBuffer均继承自抽象类AbstractStringBuilder,它完成了大部分方法的实现,因此子类只需要调用父类的方法即可
  • StringBuilderStringBuffer的默认容量都为16,并且默认的扩容大小是原来的两倍加2
  • StringBuilder不是线程安全的,而StringBuffer通过synchronized关键字保证了线程安全