类图结构
类的申明
1.默认访问控制修饰符,说明只能在包内使用,即只能在JDK内部使用,可能有人会问我创建一个java.lang包然后里面的类就可以使用AbstractStringBuilder类了,想法不错,但jkd不允许,会报SecurityException : Prohibited package name: java.lang。故这个类只是给StringBuffer和StringBuilder类使用的。
2.类名用abstract修饰说明是一个抽象类,只能被继承,不能直接创建对象。查了里面的方法你会发现它就一个抽象方法,toString方法。
3.实现了Appendable接口,Appendable能够被追加 char 序列和值的对象。如果某个类的实例打算接收来自 Formatter 的格式化输出,那么该类必须实现 Appendable 接口。
4.实现了Charsequence接口,代表该类,或其子类是一个字符序列。
abstract class AbstractStringBuilder implements Appendable, CharSequence {}
成员变量
char[] value;
int count;
value用于承装字符序列,count数组中实际存储字符的数量。这里的value同String类中的value不同,String类中的value是final的不可被修改,这里的value是动态的,并且可提供给外部直接操作。
构造函数
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
AbstractStringBuilder提供两个构造函数,一个是无参构造函数。一个是传一个capacity(代表数组容量)的构造,这个构造函数用于指定类中value数组的初始大小,数组大小后面还可动态改变。
其他重要方法
length
//返回已经存储字符序列的实际长度,即count的值。
@Override
public int length() {
return count;
}
capacity
//返回当前value可以存储的字符容量,即在下一次重新申请内存之前能存储字符序列的长度。
// 新添加元素的时候,可能会对数组进行扩容。
public int capacity() {
return value.length;
}
ensureCapacity
/**
* 该方法是用来确保容量至少等于指定的最小值,是该类的核心也是其两个实现类StringBuffer和StringBuilder的核心。
* 通过这种方式来实现数组的动态扩容。下面来看下其具体逻辑。
* @param minimumCapacity
*/
//1.判断入参minimumCapacity是否有效,即是否大于0,
//大于0执行ensureCapacityInternal方法,小于等于0则忽略。
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
//2.判断入参容量值是否比原容量大,如果大于原容量,执行扩容操作,实际上就是创建一个新容量的数组,
// 然后再将原数组中的内容拷贝到新数组中,如果小于或等于原容量则忽略。
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
/**
* The maximum size of array to allocate (unless necessary).
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by the same amount + 2 if
* that suffices.
* Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
* unless the given minimum capacity is greater than that.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero or
* greater than Integer.MAX_VALUE
*/
/**
* 3.计算新数组的容量大小,新容量取原容量的2倍加2和入参minCapacity中较大者。
* 然后再进行一些范围校验。新容量必需在int所支持的范围内,之所以有<=0判断是因为,
* 在执行 (value.length << 1) + 2操作后,可能会出现int溢出的情况。
* 如果溢出或是大于所支持的最大容量(MAX_ARRAY_SIZE为int所支持的最大值减8),
* 则进行hugeCapacity计算,否则取newCapacity
* @param minCapacity
* @return
*/
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;
}
/**
* 4.这一步先进行范围检查,必须在int所支持的最大范围内。
* 然后在minCapacity与MAX_ARRAY_SIZE之间取较大者,
* 此方法取的范围是Integer.MAX_VALUE - 8到Integer.MAX_VALUE之间的范围。
* @param minCapacity
* @return
*/
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
/**
* 5.总结:
* 1.通过value = Arrays.copyOf(value,newCapacity(minimumCapacity));进行扩容
* 2.新容量取 minCapacity,原容量乘以2再加上2中较大的,但不能大于int所支持的最大范围。
* 3.在实际环境中在容量远没达到MAX_ARRAY_SIZE的时候就报OutOfMemoryError异常了,其实就是在复制的时候创建了数组char[] copy = new char[newLength];这里支持不了那么大的内存消耗,可以通过 -Xms256M -Xmx768M设置最大内存。
*/
trimToSize
/**
* 减少字符序列的使用空间,比如申请了100字符长度的空间,但是现在只用了60个,
* 那剩下的40个无用的空间放在那里占内存,可以调用此方法释放掉未用到的内存。
* 原理很简单,只申请一个count大小的数组把原数组中的内容复制到新数组中,
* 原来的数组由于没有被任何引用所指向,之后会被gc回收。
*/
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
setLength
/**
* 用空字符填充未使用的空间。首先对数组进行扩容,
* 然后将剩余未使用的空间全部填充为'0'字符。
* @param newLength
*/
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength) {
Arrays.fill(value, count, newLength, '\0');
}
count = newLength;
}
charAt
/**
* 获取字符序列中指定位置的字符,范围为0到count,
* 超出范围抛StringIndexOutOfBoundsException异常。
* @param index
* @return
*/
@Override
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
codePointAt
/**
* 获取字符序列中指定位置的字符,所对应的代码点,即ascii码。
* @param index
* @return
*/
public int codePointAt(int index) {
if ((index < 0) || (index >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, count);
}
codePointBefore
/**
* 获取字符序列中指定位置的前一个位置的字符,所对应的代码点。
* @param index
* @return
*/
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
getChars
/**
* 将字符序列中指定区间srcBegin到srcEnd内的字符拷贝到dst字符数组中从dstBegin开始往后的位置中。
* @param srcBegin
* @param srcEnd
* @param dst
* @param dstBegin
*/
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");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
setCharAt
/**
* 设置字符序列中指定索引index位置的字符为ch。
* @param index
* @param ch
*/
public void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
value[index] = ch;
}
append系列
以append(String str)为例
/**
* 1.首先判断所传参数是否为null,如果为null则调用appendNull方法,实际上就是在原字符序列后加上"null"序列。
* 2.如果不为null则进行扩容操作,最小值为count+len,这一步可能增加容量也可能不增加,当count+len小于或等于capacity就不用进行扩容。
* 3.然后再将参数的字符串序列添加到value中。
* 4.最后返回this,注意这里返回的是this,也就意味者,可以在一条语句中多次调用append方法,即大家所知的方法调用链。原理简单,但思想值得借鉴。asb.append("hello").append("world");
* @param str
* @return
*/
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;
}
delete
/**
* 删除字符序列指定区间的内容。这个操作不改变原序列的容量。
* @param start
* @param end
* @return
*/
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;
}
deleteCharAt
/**
* 删除字符序列中指定索引index位置的字符
* @param index
* @return
*/
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;
}
replace
/**
* 将原字符序列指定区间start到end区间内的内容替换为str,
* 替换过程中序列长度会改变,所以需要进行扩容和改就count的操作。
* @param start
* @param end
* @param str
* @return
*/
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);
System.arraycopy(value, end, value, start + len, count - end);
str.getChars(value, start);
count = newCount;
return this;
}
substring
/**
* 切割原字符序列指定区间start到end内的内容,返回字符串形式。
* @param start
* @return
*/
public String substring(int start) {
return substring(start, count);
}
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
return new String(value, start, end - start);
}
indexOf
/**
* 查询给定字符串在原字符序列中第一次出现的位置。调用的其实是String类的indexOf方法
* @param str
* @return
*/
public int indexOf(String str) {
return indexOf(str, 0);
}
reverse
/**
* 该方法用于将字符序列反转
* 1.hasSurrogates用于判断字符序列中是否包含surrogates pair
* 2.将字符反转,count为数组长度,因为是从0开始的所以这里需要减1。具体转换是第一个字符与最后一个字符对调,第二个字符与倒数第二个字符对调,依次类推
* 3.实际上上述操作只需要循环(n-1) /2 + 1次[判断条件j>=0所以要+1次,源码中>>1就是除以2]就可以了,如数组长度为9则需要循环 (9-1-1)/2 +1 = 4次,9个字符对调次,第5个位置的字符不用换,如果长度为10需要循环(10-1-1)/2 +1 = 5次
* 4.剩下的工作就是两个位置的元素互换。
* 5.如果序列中包含surrogates pair 则执行reverseAllValidSurrogatePairs方法
* @return
*/
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
/**
* Surrogate Pair是UTF-16中用于扩展字符而使用的编码方式,是一种采用四个字节(两个UTF-16编码)来表示一个字符。
* char在java中是16位的,刚好是一个UTF-16编码。而字符串中可能含有Surrogate Pair,但他们是一个单一完整的字符,只不过是用两个char来表示而已,因此在反转字符串的过程中Surrogate Pairs 是不应该被反转的。而reverseAllValidSurrogatePairs方法就是对Surrogate Pair进行处理。
*/
/** Outlined helper method for reverse() */
private void reverseAllValidSurrogatePairs() {
for (int i = 0; i < count - 1; i++) {
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
toString
/**
* 需要子类自己去实现
* @return
*/
@Override
public abstract String toString();