前言
StringBuilder
与StringBuffer
是两个常用的字符串操作类,与String
的不同之处在于他们是可变的,不像String
的value
数组被final
修饰的严严实实的,而StringBuilder
和StringBuffer
的value
数组没有被final
修饰过,并且这两个类的实现几乎一样,主要的区别在于StringBuffer
的方法由synchronized
关键字修饰过,所以是线程安全的。这里先贴出整个体系的UML类图:
AbstractStringBuilder
从上面的类图也能看到,StringBuilder
和StringBuffer
均继承自抽象类AbstractStringBuilder
,AbstractStringBuilder
为子类提供了大部分的实现,因此,我们有必要先分析一下AbstractStringBuilder
的源码。
成员变量
AbstractStringBuilder
主要有以下两个成员变量,值得注意的是value
并没有被final
修饰,意味着它是可变的。
1 | // 与 String 一样维护一个字符数组 |
在这里顺便将构造函数的源码也分析了:1
2
3
4
5
6AbstractStringBuilder() {
}
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
34public 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;
}
上面的扩容操作逻辑有点复杂,这里先总结下流程:
- 默认的新容量大小为原容量大小的两倍加2,如果还不够,就直接设为所需要的容量大小
- 如果新容量大小比
MAX_ARRAY_SIZE
小,那么直接返回该新容量大小 - 否则,检查需要的容量大小是否超过整型最大值,如果超过则抛出异常
- 如果需要的容量大小比
MAX_ARRAY_SIZE
大,则直接返回需要的容量大小;否则,返回MAX_ARRAY_SIZE
其实这个扩容操作和ArrayList
的扩容操作逻辑基本一致,这里的MAX_ARRAY_SIZE
的值为Integer.MAX_VALUE - 8
也就是整型的最大值减8,那么为什么要设置成这个值呢?其实在注释中也有说明,一些虚拟机的实现可能会在数组中存储header words,因此如果分配比这个值更大的容量的话,有可能会导致OutOfMemoryError
。
append()
接下来看看append()
方法,append()
方法是整个类的核心,我们在实际中也经常使用。实际上它有很多个重载方法,这里就只分析参数为String
类型的方法。
1 | public AbstractStringBuilder append(String str) { |
可以看到append()
方法其实逻辑挺简单的,但有两点是需要注意到的,一个是追加字符串的操作是通过String
的getChars()
完成的,但最后还是调用的System.arraycopy()
这个native方法;另一个是该方法返回的是自身this
,通过这种方式,我们可以实现append()
方法的链式调用。
StringBuilder
上面分析的AbstractStringBuilder
已经实现了大部分需要的方法了,接下来开始分析第一个子类StringBuilder
,先看看构造函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public 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()
StringBuilder
的append()
方法有非常多的重载,但其实都是调用父类AbstractStringBuilder
的方法,在上面已经分析过了,所以这里就简单看一个:1
2
3
4
5
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuffer
StringBuffer
和StringBuilder
的实现基本一样,只不过方法被synchronized
关键字修饰了,因此是线程安全的,比如下面的append()
方法:1
2
3
4
5
6
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
可以看到方法中有个toStringCache
变量,这个变量是最近一次toString()
方法的缓存,任何写操作都会将该缓存重设为null
,我们看下这个toString()
方法:1
2
3
4
5
6
7
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
4String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
总结
- 与
String
不同,StringBuilder
和StringBuffer
都是可变字符串,底层value
数组没有使用final
关键字修饰 StringBuilder
和StringBuffer
均继承自抽象类AbstractStringBuilder
,它完成了大部分方法的实现,因此子类只需要调用父类的方法即可StringBuilder
和StringBuffer
的默认容量都为16,并且默认的扩容大小是原来的两倍加2StringBuilder
不是线程安全的,而StringBuffer
通过synchronized
关键字保证了线程安全