目录
一、使用
StringBuilder类和StringBuffer类的功能差不多,且都是调用AbstractStringBuilder抽象类的方法来实现的,二者最主要的不同在于:StringBuffer类解决了线程安全问题(成员方法都加了synchronized关键字),所以,实际编码时应使用哪个类都是从线程安全及其影响(包括性能影响)来判断的,不必死记硬背。
为什么当有很多字符串拼接时,用StringBuilder类或StringBuffer类会比“+”的性能好呢?因为每加一个字符串都要新产生一个String实例,且每加完一次就会产生一个新的String实例并返回,原有的实例被丢弃,这样就会产生大量String实例,JVM产生和销毁实例都是要消耗时间和资源的;如果我们能预估拼接完后的字符串的长度,再利用StringBuilder或StringBuffer的构造方法(如:StringBuffer(int capacity))一次性地分配合适的空间,append时就只需将要拼接的字符存入原来的byte数组了,不会产生过多的无用实例,所以性能会好些。
同样,通过“+”拼接字符串时,也应考虑线程安全的问题。
二、实现原理
由“String类的使用与解析”这篇博客可知,若字符串全是拉丁字符,则byte数组的每一个元素存储一个字符, coder值为0;若字符串是Unicode字符,则byte数组的每两个元素存储一个字符,且下标较小的存储低8位,较大的存储高8位,coder值1。
如果我们向字符串“This is a test”后追加字符串“实例”,其存储形式又是怎样的呢?若为原字符串的value属性分配的空间不够了又怎么办呢?
下面我们用一个简单的实例来剖析StringBuilder类和StringBuffer类的存储机制和扩容机制:
StringBuffer buff = new StringBuffer(); buff.append("This is a test"); buff.append("实例"); |
1、实例化StringBuffer
上面的代码首先用默认的构造方法产生一个StringBuffer实例,并声明一个buff变量指向该实例。
public StringBuffer() { AbstractStringBuilder(int capacity) { } |
StringBuffer构造方法调用其父类的构造方法来处理,并传入初始容量值16,COMPACT_STRINGS的值默认是true(看String类源码),所以就直接生成了容量为16的byte数组,coder也标记为拉丁(值为0)。此时value的值如下所示:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2、添加"This is a test"字符串
此时会调用AbstractStringBuilder类的append方法来处理,核心代码如下:
int len = str.length(); ensureCapacityInternal(count + len); putStringAt(count, str); count += len; |
字符串长度的计算用“value.length >> coder()”实现,即:如果是拉丁字符,coder值为0,返回的就是value数组的长度,若是Unicode字符(UTF-16),coder值为1,返回的就是value.length/2(左移一位相当于乘以2,右移一位相当于除以2)的值。
ensureCapacityInternal方法用于处理字符串的容量,核心代码如下:
// overflow-conscious code int oldCapacity = value.length >> coder; if (minimumCapacity - oldCapacity > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder); } |
由于传入的是count(原串字符个数:0)+len(待拼接字符串的字符个数:14),即14,此时的oldCapacity等于16,所以在此不需扩容。
putStringAt方法的核心代码如下:
if (getCoder() != str.coder()) { inflate(); } str.getBytes(value, off, index, coder, end - off); |
由于原串和要拼接的串都是拉丁字符,所以直接调用getBytes方法将待拼接的字符复制到原串的value数组中。此时的value值如下:
[84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115, 116, 0, 0]
3、添加"实例"字符串
由于拼接后的字符个数(16)与原串的容量(16)相等,所以先不会执行“value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);”扩容。由于原串是拉丁字符,被拼接串是UTF-16,所以执行putStringAt方法时会调用inflate方法,核心代码如下:
private void inflate() { public static byte[] newBytesFor(int len) { public static void inflate(byte[] src, int srcOff, byte[] dst, int dstOff, int len) { } static void putChar(byte[] val, int index, int c) { |
newBytesFor方法产生一个新的byte数组,其容量是原字符串容量的2倍,putChar方法将原串中的字符复制到新生成的byte数组中,val[index++]存放低8位,val[index]存放高8位(此时的C是拉丁字符的编码,只需8位存储,右移8位后,变成0),所以处理完毕后value的值如下:
[84, 0, 104, 0, 105, 0, 115, 0, 32, 0, 105, 0, 115, 0, 32, 0, 97, 0, 32, 0, 116, 0, 101, 0, 115, 0, 116, 0, -98, 91, -117, 79]
从第0~27个元素存的“This is a test”,28~31存的是“实例”。
4、执行“value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);”进行扩容:
newLength > 2 * oldLength + 2个字符的长度:扩容后的长度是newlength
newLength < 2 * oldLength + 2个字符的长度:扩容后的长度是oldLength + 2个字符的长度