这两个类在我看来就是一个'孪生兄弟',所以这次我放到一起.
1. 概览
先看下两个类的定义信息:
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{
从表面上看,这两个类除了类名不相同,其他都是一样的.
-
final
修饰,表示String
类不能被继承. -
CharSequence
提供了对不同类型的字符数组进行只读访问的方法. -
实现
Serializable
接口,表示可序列化.
以上两点和String
没太大的区别,那看下他们所继承的AbstractStringBuilder
:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
Appndable
接口定义了一些可以向其追加char序列和值的对象的方法,而CharSequence
上面讲过了,不在重复赘述.
虽然该类是抽象类,但是抽象方法也只有一个,就一个toString()
方法,显而易见,这是一个通用的工具方法类.不同特性的子类只需要继承,重写需要的方法便行.
从AbstractStringBuilder
的类注释看出,该抽象类实现的是一个可变的字符序列,可以调某些方法进行改变.和String
一样,也是有一个char
类型的数组.
既然是通用的工具方法,那具体的差异应该就是在子类中体现的.
2. 方法
这里我以Stringbuilder
为主进行讲解,如没有特别说明,则默认实现一样,只是在方法上StringBuffer
加了Synchronized
.(因为单线程情况下,StringBuilder
速度更快,而且StringBuffer
有时候并不是'同步的').
构造函数就不讲了,都是默认的16
长度,或者是传参字符串长度加上16
.
主要的是append()
和insert()
一个是追加,一个是插入.在抽象类中,有很多append()
和insert()
的重载方法,这里只挑一些关键的,其他都是大同小异.
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
- 如果为则会添加
n,u,l,l
四个字符,这个方法点进去就能看到. - 主要的是
ensureCapacityInternal(count + len)
扩容方法.
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,newCapacity(minimumCapacity));
}
}
判断是否有足够的容量容下需要添加的内容.没有则需要扩容newCapacity()
:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
如果需要的容量比原来的两倍加二还要大,则新容量以需要的为准,否则就是两倍加二.最后一步也就是校验长度,可能会存在内存溢出.
扩容完了,接下类就是将值添加进去了:
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
使用的是System.arraycopy()
的方法进行复制.
insert()
方法大致步骤也是如此,只不过在最后System.arraycopy()
时,参数传递的差别便是了.
还有个比较有意思的地方:
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
在StringBuffer
的toString
中使用了一个缓存数组,可以提高获取String
的速度,原因在于他所使用的String
构造函数不涉及数组的复制.
3.杂谈
这两个类在append()
的方法中,都有构造者模式的特点,以至于可以采用链式写法.
关于上面讲的StringBuffer
有时候并不是真正的'同步',也主要是体现在多次调用StringBuffer
的不同方法的情况下,这一系列操作,并不能保证原子性.
在实际情况下,StringBuffer
用得还是比较少的,而另一个官方也在单线程下推荐.主要也是用在大量字符串拼接的场景,类似循环拼接之类的.如果使用String
则会导致频繁的创建String
实例,进而对内存造成影响.
不难发现,在这些源码中,方法写得非常好,考虑的比较周到(重载用得好),包括一些抽象父类的设计.