一,首先聊一下String与StringBuffer
两个类均由public final修饰,意味着是最终类,不能被继承,但是为啥StringBuffer长度和内容可变,而String不可变呢?
我们看String的源码中的成员变量:
/** String本质是个char数组.*/
private final char value[];
使用了final修饰,一旦创建,该值就不可改变,但是注意final修饰引用类型的变量时,指的是引用类型地址值不能发生改变。但是Array数组是可变的,如果我们不改变指向value[]的地址,而改变value[]的值呢?此时需要注意value[]是采用private修饰的,后面String所有的方法没有动value[]里的元素,而且String类不可被继承,不能重写方法,所以String不可变。
那为啥StringBuffer长度和内容可变?
发现StringBuffer没有value[]成员变量,看父类AbstractStringBuilder中value[]成员变量
char[] value;
豁然开朗,那就看看StringBuffer的append()方法,了解一下是如何改变添加的
二,查看StringBuffer的append()方法
为了好说明,举个例子:
StringBuffer stringBuffer = new StringBuffer("hello");
stringBuffer.append("world");
/*打印出helloworld*/
System.out.println(stringBuffer);
1.进入append方法:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
其中toStringCache是啥呢?看描述是Cleared whenever the StringBuffer is modified.当改变就清空,接下来看调用了父类的append的方法【注意用synchronized修饰了,线程安全】
2.进入AbstractStringBuilder的append()方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
3.进入ensureCapacityInternal()方法
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
一目了然。
copyOf(char[] original, int newLength)
的方法查JDK帮助文档可知:
复制指定的数组,复制具有指定的长度。也就是说 ensureCapacityInternal()方法其实是重新复制了一个新数组,将长度扩大成传入的参数长度,并重新赋值给value
4.进入String的getChars()方法
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {//0,len=5,value=[hello],count=5
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的方法:将指定源数组中的数组从指定位置复制到目标数组的指定位置。
public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)
/*src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目的地数据中的起始位置。
length - 要复制的数组元素的数量。
*/
System.arraycopy([world], 0, [hello], 5, 5);
以上可知,append方法其实是创建了一个新的数组,扩大了长度,将需要添加的字符串给复制到这个新的数组中去。