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

可以看出真正进行“增长字符串”的操作是由系统方法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方法的帮助下,将各个字符引用连接起来。

发布了4 篇原创文章 · 获赞 0 · 访问量 226
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览