StringBuffer是可变的还是不可变的?

👁️ 407 ❤️ 103
StringBuffer是可变的还是不可变的?

前言:我们知道String类的修饰符是final,其char[] value也是由final修饰的,每次给String变量赋一个新值,都会创建一个新的String对象,很多有涉及到字符串本身的改变都是伴有(new String)的字样,所以我们说String类是不可变类。但StringBuffer类也是由final修饰的呀,为什么它就是可变类了呢?

public final class StringBuffer

extends AbstractStringBuilder

implements java.io.Serializable, CharSequence

{ @Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this;

}

}

在此处可以看出StringBuffer的append功能是继承其父类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;

}

private void ensureCapacityInternal(int minimumCapacity) {

// overflow-conscious code

if (minimumCapacity - value.length > 0)

expandCapacity(minimumCapacity);

}

void expandCapacity(int minimumCapacity) {

int newCapacity = value.length * 2 + 2;

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);

}

此处我们可以知道,在AbstractStringBuilder中append方法的实现,是确定容器"capacity"的大小,便于下面value(在StringBuffer中value是没有final修饰符的)的重新赋值,所以这一步我们可以看作类似“踩点”的行为。接下来才是真正的力量:

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;

}

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);

}

* @param src the source array.

* @param srcPos starting position in the source array.

* @param dest the destination array.

* @param destPos starting position in the destination data.

* @param length the number of array elements to be copied.

* @exception IndexOutOfBoundsException if copying would cause

* access of data outside array bounds.

* @exception ArrayStoreException if an element in the src

* array could not be stored into the dest array

* because of a type mismatch.

* @exception NullPointerException if either src or

* dest is null.

*/

public static native void arraycopy(Object src, int srcPos,

Object dest, int destPos,

int length);

可以看出真正进行“增长字符串”的操作是由系统方法arraycopy实现的,其中:

value是传入的String str的值,也就是要添加进来的字符串;

srcBegin是指,指定的字符数组dst要在value的哪个位置插入;

dst是将要被插入的字符数组;

dstBegin是字符数组的起始位置;

srcEnd-srcBegin是value的长度。

也就是说,StringBuffer的append的实现其实是System类中的arraycopy方法实现的,有点好奇arraycopy方法是怎么实现的,参考了https://blog.csdn.net/angjunqiang/article/details/42031351的图解,这里的浅复制就是指复制引用值,与其相对应的深复制就是复制对象的内容和值:

所以我们可以知道,StringBuffer为什么是可变类。因为StringBuffer的append方法的底层实现就是创建一个新的目标对象,然后将各个字符引用串接起来,这就是一个新的对象了,本质上就是栈多了一个变量,而堆上面并没有变化(为什么是在栈上呢?因为触发到的arraycopy方法是native的,也就是其生成的变量会放到本地方法栈上面去)。

那为什么我们没有把StringBuffer划分为不可变类呢?它明明在栈中创建了一个新的对象。因为一般的对象和数组都会在堆中进行存储,除非是发生了栈上分配现象。我们相比较String对象的存储,就可以知道,StringBuffer对象在此处并不符合栈上分配的条件( 将线程私有的对象打散分配在栈上,可以在函数调用结束后自行销毁对象,不需要垃圾回收器的介入,有效避免垃圾回收带来的负面影响,栈上分配速度快,提高系统性能),所以我们可以知道,StringBuffer的append方法并不会在堆上创建新的StringBuffer对象且内容是结果字符串,而是在arraycopy方法的帮助下,将各个字符引用连接起来。

结论:StringBuffer是一个可变类。

← 八字月干代表什么 八字中的月支是什么意思 手机的进货价格是多少 →