一、简述
StringBuilder是一个可变的字符序列,提供和StringBuffer类似的API,但是在多线程下不能保证安全,为什么是可变的以及不安全,这个后面会说,面试经常问及原理
它用于单个线程正在使用的字符缓冲序列。效率要比StringBuffer更快。
二、可变原理以及和String对比
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
//字符序列已使用容量
int count;
AbstractStringBuilder() {
}
StringBuilder其实是AbstractStringBuilder抽象类的子类,它的父类里定义了两个变量,一个char类型的数组value,以及整数型的count。
value用来存放字符序列,且并没有被fianl修饰,所以它的字符是可变的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
它与String类的区别就在于String类里的value被final修饰,所以不可变
三、append(String str)方法源码解析
StringBuilder提供了很多append重载方法,参数类型丰富,实现原理大致相同,这里以常用的String参数作解析
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
实际是调用了父类AbstractStringBuilder的append(str)方法;
//入参str就是要追加的字符串
public AbstractStringBuilder append(String str) {
//如果是null就追加个空内容
if (str == null)
return appendNull();
//不是null,首先获取追加字符串的长度
int len = str.length();
//紧接的方法是保证char数组的容量足够去追加新的字符串,这个方法在3.1里分析
ensureCapacityInternal(count + len);
//然后调用getChars()方法,这个是最终追加字符串的方法,在3.2里分析
str.getChars(0, len, value, count);
//最后将字符序列数组已用大小加上追加字符串的长度大小
count += len;
return this;
}
最后一步count += len;可以看出StringBuilder是线程不安全的,因为此处是非原子性操作。count值可能没及时更新到主内存就被其他线程读取使用,导致安全性问题。
3.1 ensureCapacityInternal()方法入参是value数组的初始容量加上待追加字符串的长度,定义为最小容量。也就是追加字符串后value数组的最小容量。
private void ensureCapacityInternal(int minimumCapacity) {
// 这里判断最小容量是不是比初始容量还要大,初始容量是16。逻辑成立则调用newCapacity()方法进行扩展得到新的数组容量
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// 将初始数组容量扩容到原来的两倍并加2,也就是34
int newCapacity = (value.length << 1) + 2;
//如果扩容后的新容量还是比最低容量小 则将新容量改为最低容量,以此满足最低需求
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//返回最终满足扩容的新容量大小,
//如果新容量比最大的数组容量(3的21次方-8)还大就返回巨型容量,否则返回自己
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
//获取巨型容量
private int hugeCapacity(int minCapacity) {
//如果最低容量比数组的极限容量(包含数组元数据,长度描述所占的8bytes,即:3的21次方)还大则抛出内存不足的异常
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
//如果最低容量比数组最大容量(不包含数组元数据,长度描述所占的8bytes即:3的21次方-8)还大则返回最低容量大小,否则返回数组最大容量
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
3.2 上面3.1调整好字符序列的容量后,开始调用getChars()方法,完成新增字符串的追加。
//srcBegin 0 表示需要待追加的字符的起始位置
//srcEnd str.length 表示需要待追加的字符的结束位置
//dst[] 原字符序列 目标源,即AbstractStringBuilder已存在的字符序列
//dstBegin 原字符序列已使用容量
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
//这里的value是str的数组序列,也就是待追加字符串。并不是AbstractStringBuilder的字符序列。
//该方法的流程就是将待追加的字符整个追加到原字符序列的末尾。如何确定末尾位置,根据dstBegin。
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
四、总结下:StringBuilder为何可变因为,字符存放的数组value没有被fianl修饰,为何不安全,因append()方法里有非原子性操作,即count += len。append()方法主要做了三件事,step1:根据情况扩容,step2:复制待追加字符到字符序列数组里,step3:更新字符序列已使用容量count
--------- 文章如有问题,请下方回复指出,感谢查阅,祝每天进步一点点 😁 -------------