StringBuilder和StringBuffer
这两个类的方法基本是完全一样的,他们的实现代码也几乎一样,唯一不同的在于StringBuffer是线程安全的,而StringBuilder不是。
下文讨论StringBuilder。
基本实现原理
与String类似,StringBuilder类也封装了一个字符数组,定义如下:
1 | char[] value; |
与String不同,它不是final的,可以修改。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
1 | int count; |
new StringBuilder()这句代码,内部会创建一个长度为16的字符数组(默认构造方法),count的默认值为0。
append的实现
源码:
1 | public AbstractStringBuilder append(String str) { |
append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展,实际使用的长度用count体现。具体来说,ensureCapacityInternal(count+len)会确保数组的长度足以容纳新添加的字符,str.getChars会拷贝新添加的字符到字符数组中,count+=len会增加实际使用的长度。
ensureCapacityInternal的代码如下:
1 | private void ensureCapacityInternal(int minimumCapacity) { |
如果字符数组的长度小于需要的长度,则调用newCapacity进行扩展,newCapacity的代码是:
1 | private int newCapacity(int minCapacity) { |
扩展的逻辑是,分配一个足够长度的新数组,然后将原内容拷贝到这个新数组中,最后让内部的字符数组指向这个新数组。
参数minimumCapacity表示需要的最小长度,需要多少分配多少不就行了吗?不行,因为那就跟String一样了,每append一次,都会进行一次内存分配,效率低下。这里的扩展策略,是跟当前长度相关的,当前长度乘以2,再加上2,如果这个长度不够最小需要的长度,才用minimumCapacity。
比如说,默认长度为16,长度不够时,会先扩展到16*2+2
即34,然后扩展到34*2+2即70
,然后是70*2+2
即142,这是一种指数扩展策略。为什么要加2?大概是因为在原长度为0时也可以一样工作吧。
为什么要这么扩展呢?这是一种折中策略,一方面要减少内存分配的次数,另一方面也要避免空间浪费。在不知道最终需要多长的情况下,指数扩展是一种常见的策略,广泛应用于各种内存分配相关的计算机程序中。