StringBuffer原理
可以从StringBuffer源码看出,StringBuffer主要继承于一个抽象类
我们看StringBuffer的实现方法,然而并没有什么用,里面的实现大多数是调用了父类的方法了,所以去拜访父类AbstractStringBuilder
从上图可以看出,AbstractStringBuilder
只有两个成员变量,第一个char数组主要是存储真正懂得字符串,第二个是记录已经使用的长度,应为value开辟的空间一般是大于实际保存的,如果char数组的空间不够用的时候,需要用到扩容机制。
下面我们开始探索所有函数
构造函数
从构造函数可以看出StringBuffer调用父类AbstractStringBuilder类的构造方法,默认的字符数组长度是16,当然在StringBuffer在构造时候可以穿一个整数指定长度。
添加字符串
我们先看append方法的源码,都可以看到在添加的时候,调用了方法ensureCapacityInternal
扩容机制
传入了当前数组长度加上需要添加的长度,也就是添加元素后,字符数组将要改变为多少。所以我们不得不转入这个方法。
可以看到,他首先检查了前面传入的长度如果大于实际字符数字的长度,说明此时实际的数组长度不够用了,需要将数组长度扩大。
首先补充一下,说下方法copyOf
这个是Arrays类提供的静态方法,将原来的数组扩充到newLength长度,然后将原来的数据拷贝过去,可以从源码看出,它首先创建出一个新的字符数组,然后使用System的静态方法arraycopy将原来的数据拷贝到新的数组,然后返回新的数组。
也顺便看看arraycopy方法,他是native方法,所以说没有java实现,他是调用了c++写的方法,所以说为什么不使用for循环一个一个的将字符复制,而是使用了arraycopy的原因是这个方法原则来说快。
说完之后,我们再回到ensureCapacityInternal方法,把上面的图继续放到我们眼前。
可以看到将扩容后的数组由copyOf返回后,又赋值给了value,然后突然发现到底扩容到多大,是数据传多长,就把他扩容到多大吗,显然不是,如果这样的话,每次添加元素都需要扩容,每次重新分配char数组,分配空间效率大大降低,所以需要一部分备用空间。需要多大的备用空间呢,看方法newCapacity
首先第一句算出了需要扩充到的空间长度(value.length << 1) + 2
这句话的<<
就是二进制移位计算,左移相当于*2,右移相当于/2,使用移位的话,效率高,最后计算的长度就是原来长度翻倍然后+2(为什么还要加个2我还不明白),
计算出预判的长度后,和需要的长度去比较,如果我扩大2倍多还比需要的短,那就是来捣乱的,一下子增加太多了,那么他要多少就给多少。然后最后返回了一个三元表达式,分析这个三元表达式。
newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0
这里newCapacity <= 0 就是当传入的minCapacity < -2的负数时候成立
这里MAX_ARRAY_SIZE - newCapacity < 0 当计算出来的长度比MAX_ARRAY_SIZE长度还长就成立,MAX_ARRAY_SIZE在源码可以看到
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果满足上面的条件就,会去找hugeCapacity,不是的话直接返回之前计算的长度。
可以看出,超过整数范围就跑错,没有的话返回一个三元表达式,分析一下
前面已经判断了minCapacity没有超过整数范围,而MAX_ARRAY_SIZE是最大整数减去8,所以可以得出
Integer.MAX_VALUE - 8 < minCapacity < Integer.MAX_VALUE 返回minCapacity,这里以及是用到了极限了,这次之后再无法扩容了,一扩容就报错。
1 <= minCapacity <= Integer.MAX_VALUE - 8 返回Integer.MAX_VALUE - 8
思考
这里我们就思考了,弄这么多,到底干啥了,其实就是如果newCapacity方法计算的空间如果不是负数或者小于Integer.MAX_VALUE - 8的数,那么就正常走,使用计算的扩容长度,如果newCapacity方法计算的空间如果是负数或者大于Integer.MAX_VALUE - 8的数,那么如果超了整数范围,那么报错,没有的话如果超了Integer.MAX_VALUE - 8,那么使用极限长度,没有超则使用Integer.MAX_VALUE - 8,但是前提是传过来的数肯定超出了,所以这个函数就是使用了极限范围。
在最后计算到合适的长度后,扩充容量到所计算的范围。
添加字符串
继续回到添加元素的方法,首先调用ensureCapacityInternal对容量进行处理,需要扩容的时候扩容。
然后调用字符串的getChars方法,我们看这个方法
可以看出,前面做了一些异常判断,然后调用字符数组拷贝方法,将str的数据拷贝过去,最后将计数器count+1。
删除部分字符串
删除字符串使用的是覆盖数据的方法
原理图
替换字符串
替换首先是将原来value在end开始之后的字符往后移动**字符串长度-(end-start)**长度,替换成字符数组拷贝的方法,也就是说将end开始到count-end(这里表示替换位置之后剩余的部分)拷贝到从start+len开始的地方(这里表示给字符串腾出的位置的结尾开始拷贝剩余的字符串)拷贝,然后将需要替换的字符串填充在腾出的空间上。
原理图
插入字符串
原理图
总结
可以看出,删除,替换,插入操作肯定用到了数组的复制,添加可能会数组扩容
回到StringBuffer
谈谈toStringCache字段
首先transient说明这个字段无法被序列化
这个主要还是为了保存ToSring执行生成的字符数组,为了提高效率,如果字符串没有改变的情况下,直接返回。
可以看到每次修改都会将toStringCache字段赋值为空,在调用ToString后就会获取到修改后的字符串,而不会走缓存。
最后附加继承图