StringBuilder与StringBuffer

概述

字符串作为最常用的类型之一,java除了提供不可变的String类之外,还拥有可变字符串StringBuilder与StringBuffer。两者最大的区别在于StringBuilder不保证线程安全,而StringBuffer则将方法以synchronized修饰。所以可以保证线程安全。

除此之外,两者都继承了AbstractStringBuilder类,所拥有的方法近乎完全相同。作为抽象类,AbstractStringBuilder实现了Appendable与CharSequence接口。

值得注意的是,与StringBuilder相比,StringBuffer中的toString方法,利用了char[] toStringCache缓存来提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;

@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}

// 如果上次toString之后字符串没有变化,则利用缓存直接返回
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}

为什么StringBuilder不采用toStringCache呢?参考stackoverflow,大意是StringBuiler不提供线程同步,而在保持线程不同步的性能优势下,提供缓存极难实现。并且,这种缓存优化现实中使用很少。

由于StringBuilder与StringBuffer的差别很小,所以两者很多方法的具体实现,都放在了共同的父类AbstractStringBuilder中。本文姑且从AbstractStringBuilder入手,对字符串操作略作分析。

导航

append

AbstractStringBuilder中重载了大量的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
27
28
29
30
31
32
33
34
35
36
37
// 字符存放,一个字符2-byte
byte[] value;

// 编码(实际值由JVM注入),LATIN1(=0) or UTF16(=1)
byte coder;

// 字符数
int count;

// 链式方法,可以 stringBuilder.append().append()
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len); // 保证value数组的最小容量(字符数),见下文
putStringAt(count, str); // append字符串
count += len;
return this;
}

private final void putStringAt(int index, String str) {
if (getCoder() != str.coder()) { // this.coder与str.coder不等
inflate(); // value膨胀byte[]至utf16编码
}
str.getBytes(value, index, coder); // str添加至value[index]后
}

private void inflate() {
if (!isLatin1()) { // utf16直接返回
return;
}
byte[] buf = StringUTF16.newBytesFor(value.length);
StringLatin1.inflate(value, 0, buf, 0, count); // Latin1->utf16
this.value = buf;
this.coder = UTF16;
}

ensureCapacity(int minimumCapacity)

Array在添加元素的时候,免不了容量的限制。所以以byte[]来保存value,也需要对容量进行额外的保证。

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
private void ensureCapacityInternal(int minimumCapacity) { // 最少字符(2-byte)数
// overflow-conscious code
int oldCapacity = value.length >> coder; // utf16一个单元占1或2字符,这里算value可能的最少字符数
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}

// 返回字符数
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder; // 同ensureCapacityInternal中
int newCapacity = (oldCapacity << 1) + 2; // 扩至2n+2=2*(n+1)
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder; // 稳定安全状态下,最大字符数
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0) // newCapacity过大
? hugeCapacity(minCapacity)
: newCapacity;
}

private int hugeCapacity(int minCapacity) {
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
int UNSAFE_BOUND = Integer.MAX_VALUE >> coder;
if (UNSAFE_BOUND - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > SAFE_BOUND)
? minCapacity : SAFE_BOUND;
}

reverse()

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public AbstractStringBuilder reverse() {
byte[] val = this.value;
int count = this.count;
int coder = this.coder;
int n = count - 1;
if (COMPACT_STRINGS && coder == LATIN1) {
// 以中间为分界线,调换左右两边
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
byte cj = val[j];
val[j] = val[k];
val[k] = cj;
}
} else {
StringUTF16.reverse(val, count);
}
return this;
}