目录
1.2StringBuffer类,它与builder继承自同一超类AbstractStringBuilder
1.2.3重点分析方法(与上述Builder中的方法的同名方法和toString方法)
2.1StringBuilder和StringBuffer是如何做到长度可变:
1正文
1.1以StringBuilder类为例
1.1.1成员属性
value没有被final修饰,所以可以多次赋值,改变其指向对象,这点与String有着本质的区别。
//都是其父类的成员属性
char[] value;//内部维护的数组
int count;//数组内被字符占用的下标个数
1.1.2四种构造方法
//初始化内部维护的数组长度16,内容为null
public StringBuilder() {
super(16);
}
//指定长度
public StringBuilder(int capacity) {
super(capacity);
}
//借助String对象实例化该对象,长度比其长16
public StringBuilder(String str) {
super(str.length() + 16);
append(str);//后面分析
}
//借助CharSequence对象,不为本文重点
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
1.1.3详细分析的方法
append重载方法有很多,本文只以其中一个为例
//将任意对象转为String后添加
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
//本文重点攻克方法
public StringBuilder append(String str) {
super.append(str);
return this;
}
//父类的append方法
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);//字符总个数
str.getChars(0, len, value, count);将str的全部复制到value的开始下标count之后
count += len;
return this;
}
//如果数组已用下标+新加入字符长度比数组总长度要大,
//即数组可用空间小了,就创建新数组
//新数组长度为之前两倍加2,如果还不够:
//新数组长度就为数组已用下标+新加入字符长度
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));//避免新数组长度过长或过短
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)//得到一个数组最大容量的数
: newCapacity;
}
//从dst的dstBagin开始,将调用对象的srcBagin到srcEnd范围字符复制过去
//即将str的全部复制到value的开始下标之后
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);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
System.arraycopy(a[],b,c[],d,f)方法不仅在append方法中被调用,在delete,insert,replace,以及Arrays类的copyOf等对数组进行操作的方法中也被调用,它的功能是:将从a的b下标开始遍历f长度的元素放到c的d下标之后的位置上。
1.2StringBuffer类,它与builder继承自同一超类AbstractStringBuilder
1.2.1成员属性
除了继承父类的两个成员属性外,还有一个专有属性:缓冲数组,用于储存缓冲数据,在调用toString方法时初始化
private transient char[] toStringCache;
1.2.2构造方法
与builder没有差异
1.2.3重点分析方法(与上述Builder中的方法的同名方法和toString方法)
上述builder中的方法与Buffer的方法的区别:
1.每次对value进行增删改操作时都会将Buffer里面的缓存数组清零
2.Buffer是线程安全的,原因就在于这些对value操作的方法都被锁上了
例如:
//和builder相比多出了一个将缓冲数组清零的语句
//以及多了一个锁
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
此外Buffer中的toString方法也比较特殊:
它在调用时会将value内的字符串缓存到toStringChche数组上,再次调用时就不需再次遍历数组,直接给实例化的String对象内部维护的数组赋值即可,减少了StringBuffer再次转换为String时的遍历过程。
(如果是使用new String(StringBuffer sb)的方式将StringBuffer转为String,会调用Arrays.copyOf(...)方法,这个方法会调用System.arraycopy(...)方法,遍历一个数组给另一个数组添加元素,再次实例化时依旧再走一遍这个流程,每实例化一次遍历一次)
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
2总结(即回答标题问题)
2.1StringBuilder和StringBuffer是如何做到长度可变:
他们的数组长度并不是正好等于内部字符个数,而是有一个固定值,一旦加入字符数组容纳不下便会对数组进行扩容,此时会有新的数组对象产生,扩容的长度不少于原值的两倍+2(达到数组容量上限除外)
2.2两者的区别
它们的主要区别有两个:
一是两者对线程的安全性不同,和StringBuffer内部对数组进行修改操作的方法都被synchronized关键字锁上了,所以它是同步的、安全的但速度较慢的。
二是StringBuffer具有缓冲机制,它在调用toString方法时将此时的内部数组保存到缓存数组上,在没有调用修改数组的方法时(这些方法会还原缓存数组),下一次再次调用就直接将缓存数组赋值给String内部的数组类型的变量