AbstractStringBuilder源码分析
本篇博客是基于JDK1.8的源码分析
最近在阅读StringBuilder
和StringBuffer
的源码时,发现它们都实现了AbstractStringBuilder
这个抽象类,且在这个抽象类中对方法都进行了默认实现,而StringBuilder
和StringBuffer
的方法都采用了AbstractStringBuilder
的默认实现。出于好奇心,对AbstractStringBuilder
的部分源码进行阅读。
一、初步分析
字段分析
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
}
可以看到,AbstractStringBuilder
底层依然是使用char[]
,但未被final
修饰,说明这个char[]
可以被改变,这是与String
的区别之一。其次是count
,表示char[]
数组中被占用的个数,通俗地说,就是字符串的长度。
构造方法分析
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
因为AbstractStringBuilder
被 abstract
所修饰,表示其是一个抽象类,而抽象类是不能直接调用构造方法创建对象,所以在这里讨论也没意义。
二、常用方法分析
length() 和 capacity()
public int length() {
return count;
}
public int capacity() {
return value.length;
}
很明显,length()
返回的是字符串的长度,而capacity()
返回的是char[]
的容量(大小)。
charAt 和 setCharAt
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
public void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
value[index] = ch;
}
charAt(int index)
传入索引,根据索引位置返回char[]
中该位置的字符。setCharAt(int index, char ch)
根据指定索引
和字符
,修改char[]
中指定位置的字符。
getChars
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
// 重点是最后一句起作用,可以查看API
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
这个函数主要作用是将value
中的字符按照指定的起始位置复制到dst
目标数组中。像Srting
、StringBuilder
和Stringbuffer
中都有这个方法,它们效果都是一样的,还是作用非常大的一种方法。
append
这个我已传入参数为String
类型的例子作为此次分析。
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;
}
在确认有着足够的容量支撑此次操作之后,调用String
的getChars()
,将str
内部的char[]
的数据全部复制到value
的count
位置,代表着字符串最后。传入参数为StringBuilder
、StringBuffer
原理都是一样的,都是将需要连接的字符串的char[]
复制调用者的value
中。
appendNull() 和 append(boolean b)
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;
}
public AbstractStringBuilder append(boolean b) {
if (b) {
ensureCapacityInternal(count + 4);
value[count++] = ‘t‘;
value[count++] = ‘r‘;
value[count++] = ‘u‘;
value[count++] = ‘e‘;
} else {
ensureCapacityInternal(count + 5);
value[count++] = ‘f‘;
value[count++] = ‘a‘;
value[count++] = ‘l‘;
value[count++] = ‘s‘;
value[count++] = ‘e‘;
}
return this;
}
appendNull
和append(boolean b)
较为特殊。这两种方法是在后面添加null
、true
、false
。值得注意一下。
append(int)
public AbstractStringBuilder append(int i) {
if (i == Integer.MIN_VALUE) {
append("-2147483648");
return this;
}
// 获取需要添加"字符串"的长度
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
// 依然使用 getChars 进行字符串的复制
Integer.getChars(i, spaceNeeded, value);
count = spaceNeeded;
return this;
}
如果参数为其余的基本类型,则需要做额外的判断和处理。也可以看出getChars
在字符串操作中占据很大的位置。
delete(int start, int end)
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
删除指定起始位置的字符。但其实不是真正意义上的删除,而是将end
以后的字符复制到从start
开始的位置,并且将count
修改为count - len
,目的是让字符串 “看起来” 被删除了,实际上除了start ~ (count-end)
的字符改变了,其余位置并没有改变。
deleteCharAt(int index)
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
重点还是使用System.arraycopy()
方法,将index
之后的字符复制到index
处,达到 “删除” index
位置的字符的效果。
replace(int start, int end, String str)
public AbstractStringBuilder replace(int start, int end, String str) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
int len = str.length();
// 计算新的长度
int newCount = count + len - (end - start);
ensureCapacityInternal(newCount);
// 预留出 len 个字符
System.arraycopy(value, end, value, start + len, count - end);
// 将str中的字符 复制到 start开始的位置。
str.getChars(value, start);
// 修改字符串长度
count = newCount;
return this;
}
关于其他方法
AbstractStringBuilder
其他方法的实现与String
中的方法大同小异,这里就不在赘述了。
三、AbstractStringBuilder中的扩容机制
ensureCapacity() 和 ensureCapacityInternal()
// 自己设置需要的容量时会用到
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));
}
}
在append()
方法中,我们经常看到ensureCapacityInternal
这个方法,这个方法是为了判断容量是否足以支撑此次append()
操作,如果不进行判断,可能会出现越界异常
。而当容量不够时,AbstractStringBuilder
会进行扩容,也就是newCapacity()
方法。
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;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
minCapacity
是设定需要的容量,默认扩容的大小是 capacity * 2 + 2
,但是如果需要的容量大小大于newCapacity
,那么以minCapacity
为准,用户可以通过调用ensureCapacity()
方法自己设定容量大小。如下所示:
StringBuilder str= new StringBuilder("aaa");
str.ensureCapacity(20);
System.out.println(str.capacity());
// 输出结果为 40
StringBuilder
的默认容量是16,但这仅仅是空参构造函数,如果传入String
类型的参数,默认容量则是字符串长度+16
,就上面而言,str
的初始容量是19。再调用str.ensureCapacity(20)
语句,就是先判断需要的最小容量是否小于当前容量,如果小于,则不做处理。如大于,则进行扩容。
很容易得出结果,上述程序的结果为40。
还存在一种情况,就是当int newCapacity = (value.length << 1) + 2;
这句话导致newCapacity
溢出时,执行进一步的判断,而hugeCapacity()
就是根据minCapacity
计算容量。
trimToSize()
在查看源码时,还发现一个有趣的方法,也就是trimToSize()
。
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
这个方法和扩容恰恰相反,缩小容量只当前字符串大小,如果执行以下语句:
StringBuilder str= new StringBuilder("aaa");
str.ensureCapacity(20);
System.out.println(str.capacity()); // 40
str.trimToSize();
System.out.println(str.capacity()); // 3
这个方法知道即可,可以缩小value
所占的空间,达到空间最大利用化。
四、总结
到此,AbstractStringBuilder
的主要源码部分就已经阅读完了,如有出错的地方,请指出让我改正。