AbstractStringBuilder

AbstractStringBuilder源码分析

本篇博客是基于JDK1.8的源码分析

最近在阅读StringBuilderStringBuffer的源码时,发现它们都实现了AbstractStringBuilder这个抽象类,且在这个抽象类中对方法都进行了默认实现,而StringBuilderStringBuffer的方法都采用了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];
}

因为AbstractStringBuilderabstract所修饰,表示其是一个抽象类,而抽象类是不能直接调用构造方法创建对象,所以在这里讨论也没意义。

二、常用方法分析

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目标数组中。像SrtingStringBuilderStringbuffer中都有这个方法,它们效果都是一样的,还是作用非常大的一种方法。

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;
}

在确认有着足够的容量支撑此次操作之后,调用StringgetChars(),将str内部的char[]的数据全部复制到valuecount位置,代表着字符串最后。传入参数为StringBuilderStringBuffer原理都是一样的,都是将需要连接的字符串的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;
}

appendNullappend(boolean b)较为特殊。这两种方法是在后面添加nulltruefalse。值得注意一下。

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的主要源码部分就已经阅读完了,如有出错的地方,请指出让我改正。

AbstractStringBuilder

上一篇:在Ubuntu上安装Docker


下一篇:文件管理基础命令之二