StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer的char数组是可变的。
关于线程安全问题就要看他们的jdk源码了。
1. StringBuilder
- 我们直达append()方法
public StringBuilder append(String str) {
super.append(str);
return this;
}
- 因为StringBuilder继承了AbstractStringBuilder,找到父类的append()方法
变量解释:
//存储字符串的数组
char[] value;
//已经使用的字符数组的数量
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
//确保数组容量充足
ensureCapacityInternal(count + len);
//把str拷到value数组
str.getChars(0, len, value, count);
count += len;
return this;
}
我们可以发现count += len不是原子操作,在有多个线程操作的时候,会发生与期望值相悖的情况,造成总的count比期望的小。
第二个问题是会有可能抛出ArrayIndexOutOfBoundsException异常,比如有两个线程进行append操作,线程1进行ensureCapacityInternal扩容之后时间片用完,这时线程2执行append,刚好把容量用完,等线程1执行str.getChars时,就会抛出ArrayIndexOutOfBoundsException异常。
- 补充:
当容量不够时,扩容是原来2倍长再加2,如果还是不够则取minimumCapacity,最后新建数组把值拷贝过去
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
//扩容是原来2倍长再加2
int newCapacity = value.length * 2 + 2;
//新容量比str.length()+value.length短的话
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//创建一个新的数组,把值拷贝过去
value = Arrays.copyOf(value, newCapacity);
}
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
2. StringBuffer
和StringBuilder一样,StringBuffer也是AbstractStringBuilder的子类。不同的是,StringBuffer在append和delete操作时加了同步锁。
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
参考: