- StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
- StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
StringBuffer和StringBuilder在底层实现和内存结构上是一样的,它们的方法的作用也是一样的,只不过StringBuffer里的方法都被声明为了同步方法(用synchronized关键字修饰),所以才产生了线程安全和线程不安全的问题。
由于它们的底层实现内存结构都是一样的,所以现在以StringBuffer为例进行说明。
1.StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过capacity()方法来获取当前实体的实际容量。
2.StringBuffer(int size)可以指定分配给该对象的实体的初始容量参数为参数size指定的字符个数。当该对象的实体存放的字符序列的长度大于size个字符时,实体的容量就自动的增加。以便存放所增加的字符。
3.StringBuffer(String s)可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符。当该对象的实体存放的字符序列长度大于size个字符时,实体的容量自动的增加,以便存放所增加的字符。
StringBuffer构造器的源码
我们先来看看StringBuffer的空参构造器:
构造一个string buffer,该string buffer为空,在没有传参的情况下默认初始容量为16个字符。
即: char[16]
再看下面的例子:
StringBuffer stringBuffer1 = new StringBuffer("ABC");
此时调用的是StringBuffer的有参构造,源码为:
父类的构造器:
从:super(str.length() + 16);
可以看到此时的长度在 “ABC”.length() 的基础上又加了16,即19
所以super(str.length() + 16)
这行代码的作用就是在方法区的字符串常量池把原来的 char[16] 改为了char[19]
需要注意的是:如果我们此时打印stringBuffer1的长度,会发现它不是19,而是3
StringBuffer stringBuffer1 = new StringBuffer("ABC");
System.out.println(stringBuffer1.length()); //返回的结果为:3
为什么是3呢?
我们从上述例子可以看出,有参数的情况下,初始容量是16+字符串的长度,并且是用append()方法追加的字符。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
答案就在 append() 方法里,于是我们找到append的源码:
答案就在这里,str为我们要添加的字符串,首先程序进来会先判断 str 是否为空,如果为空执行 appendNull() 方法并返回。
然后,int len = str.length()
这个len是传入的参数 (String str) 自身的长度
而str.length()
调用的是String类里的length()方法,length()方法用于返回字符串的长度,长度等于字符串中 16 位 Unicode 代码单元的数量,即char型数组中元素的个数。“ABC”为3个元素,所以返回 3 。
然后count+len后,返回count,由于一开始count是0,len是3,所以这里的count就返回3了
StringBuffer扩容
再假设我new了一个空参的StringBuffer,
StringBuffer stringbuffer2 = new StringBuffer();
然后append()超过了16的长度,底层的char型数组存不下了,会怎样呢?
这时候会扩容底层的数组
我们看到有个 ensureCapacityInternal() 的方法,该方法是的中文翻译是 “确保内部容量”。很明显这个方法就是为了保证内部容量足够而进行底层数组扩容的方法:
ensureCapacityInternal(count + len);
我们点进去源码:
minimumCapacity就是刚才传入的count+len,if语句判断,如果符合,则调用里面的逻辑
然后我们进入newCapacity(minimumCapacity)
方法:
然后发现它是这么扩容的 int newCapacity = (value.length << 1) + 2;
<< 是位运算符,相当于乘以2,即原来的位数扩容为原来的两倍,然后再加2;
这个时候如果还是放不下,那就直接扩容到它需要的长度 newCapacity = minCapacity;
小结:
扩容问题:如果要添加的数据底层数组存不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
扩容策略(2*n + 2)
StringBuffer在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer对象的时候,因为没有设置初始化字符长度,StringBuffer的容量被初始化为16个字符,也就是说缺省容量就是16个字符。
当StringBuffer达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2旧值+2)。
如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第16个字符的时候它会将容量增加到34(216+2),当追加到34个字符的时候就会将容量增加到70(2*34+2)。
StringBuffer扩容是2倍+2?为什么要+2?
请注意传入参数int,意味着这里传入参数可以是0,那么在参数是0的情况下,0<<1运算结果也是0,那么在初始化数组的时候必然会报错,所以作为设计的安全性考虑,这里防止出现报错,选择了+2,如有疑问请留言