前言

StringBuilderStringBuilder都是字符串操作类。它们都继承了AbstractStringBuilder抽象类,并且都实现了CharSequenceSerializable接口。它们和String的区别在于它们的value数组没有被final修饰符修饰,所以是可变的。它们之间的区别是StringBuffer的方法是由synchronized关键字修饰,所以是线程安全的。

image.png

AbstractStringBuilder

AbstractStringBuilderStringBuilderStringBuffer共同继承的抽象类,它为子类提供了大部分的实现。

成员变量 & 构造方法

1
2
3
4
5
6
7
8
9
10
11
12
// 维护一个字符数组
char[] value;
// 字符的个数
int count;

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

扩容

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
40
41
42
43
// minimumCapacity: 需要的最小容量
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}


private void ensureCapacityInternal(int minimumCapacity) {
// 如果所需要的容量大于当前容量,则进行扩容,使用Arrays.copyOf()
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}

// 数组长度的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private int newCapacity(int minCapacity) {
// 新容量 = 2 * 旧容量 + 2
int newCapacity = (value.length << 1) + 2;
// 如果新容量仍不够大,将新容量设置为所需要容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 如果新容量 > MAX_ARRAY_SIZE or < 0
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
// 进一步判断
? hugeCapacity(minCapacity)
// 否则,直接返回新容量
: newCapacity;
}

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

在一些虚拟机中会为数组保留一些 header words,所以为了防止OutOfMemoryErrorMAX_ARRAY_SIZE设置为整型的最大值减 8。总的来说,扩容机制的过程如下:

  1. 新容量 = 旧容量 * 2 + 2,如果新容量仍然不能满足,数组长度直接设置为所需容量。
  2. 如果新容量小于MAX_ARRAY_SIZE,直接返回新容量。
  3. 否则,当所需容量大于整型最大值时抛出异常。
  4. 如果所需容量 > MAX_ARRAY_SIZE,直接返回所需容量,否则返回MAX_ARRAY_SIZE

append()

append()是整个类的核心方法,拥有多个重载方法,这里主要分析以String为参数的方法。

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) {
if (str == null)
return appendNull();
int len = str.length();
// 扩容
ensureCapacityInternal(count + len);
// 调用String.getChars()将str添加到value末尾,本质是调用System.arraycopy()
str.getChars(0, len, value, count);
// 更新字符长度
count += len;
// 返回自身,实现链式调用
return this;
}

private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
// 添加'n', 'u', 'l', 'l'四个字符到value数组中
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}

StringBuilder

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 默认初始化容量16
public StringBuilder() {
super(16);
}

// 指定初始化容量
public StringBuilder(int capacity) {
super(capacity);
}

// 直接传入一个String
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}

// 传入一个字符序列
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}

append()

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

StringBuffer

方法实现基本和StringBuilder相同,但是被synchronized关键字修饰,保证了方法的线程安全。
append()为例:

1
2
3
4
5
6
7
// 实现和StringBuilder相同,但是添加的synchronized关键字保证了线程安全
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

toStringCache & toString()

StringBuffer的任何写方法都会对toStringCache重设为null,该变量保存了最近一次toString()方法的缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 最近一次toString()的缓存,任何写操作都会将该变量重设为null
private transient char[] toStringCache;

@Override
public synchronized String toString() {
// 如果缓存为空,则填充缓存
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
// 返回使用缓存new的String对象
return new String(toStringCache, true);
}

// 该方法不是直接复制,而是将String中的value指向toStringCache
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}

总结

  1. 可变性:
    • String是不可变的;
    • StringBuilderStringBuilder没有使用finalprivate关键字修饰底层的value数组。
  2. 线程安全性:
    • String对象是不可变的,线程安全;
    • StringBuffer的写方法具有同步锁,线程安全;
    • StringBuilder的写法发没有同步锁,线程不安全;
  3. 性能:
    • String类型改变时会生成一个新的String对象,并用指针指向它。
    • StringBuffer每次对本身进行操作。
    • 相同情况使用StringBuilder性能提高 10% ~ 15%,但是存在多线程不安全的风险。
  4. 使用:
    • String适合操作少量数据。
    • StringBuilder适合单线程操作字符串缓冲区下操作大量数据。
    • StringBuffer适合多线程操作字符串缓冲区下操作大量数据。