zoukankan      html  css  js  c++  java
  • Question 20171116 StringBuffer和StringBuilder的扩容机制

    StringBuffer和StringBuilder都是继承自AbstractStringBuilder,它们两个的区别在于buffer是线程安全的,builder是线程不安全的,前者安全效率低,后者高效不安全:它们的扩容机制也是这样的区别,所以我们只需要分析一个的扩容就可以了,分析buffer,另一个只用把synchronized关键字去掉就是一样的.

    6.1.2  StringBuffer的初始容量和扩容机制

    6.1.2.1 StringBuffer的初始容量

    既然是容器,那么是一定会有个初始容量的,目的在于避免在内存中过度占用内存.容器的初始容量有默认和使用构造函数申明两种.

    查看API我们知道StringBuffer有以下几种构造方法:

    StringBuffer() 
              构造一个其中不带字符的字符串缓冲区,其初始容量为 16 个字符。

    StringBuffer(CharSequence seq) 
              public java.lang.StringBuilder(CharSequence seq) 构造一个字符串缓冲区,它包含与指定的 CharSequence 相同的字符。

    StringBuffer(int capacity) 
              构造一个不带字符,但具有指定初始容量的字符串缓冲区。

    StringBuffer(String str) 
              构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。

    知道了构造方法,我们来了解一下StringBuffer的容量的底层原理;

    6.1.2.1.1 不声明长度,默认分配16

    查看StringBuffer的空参构造,发现其容量受父类AbstractStringBuilder控制

    public StringBuffer() {
            super(16);
        }
    AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }

    查看上面代码发现默认容量实际上就是创建了一个长度16的字符数组;

    6.1.2.1.2 声明长度

    查看StringBuffer的有参构造

    public StringBuffer(int capacity) {
            super(capacity);
        }
    public StringBuffer(String str) {
            super(str.length() + 16);
            append(str);
        }
    public StringBuffer(CharSequence seq) {
            this(seq.length() + 16);
            append(seq);
        }
    AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }

    我们发现有参构造有三个,直接给予长度的很好理解,底层是根据这个长度来创建了一个字符数组,而使用字符串的创建呢,底层的数组长度是字符串长度+16,如果是用字符序列那么和字符串是一样的,然后执行append操作.

    6.1.2.2 StringBuffer的原理

    StringBuffer继承了抽象类AbstractStringBuilder,在AbstractStringBuilder类中,有两个字段分别是char[]类型的valueint类型的count,也就是说,StringBuffer本质上是一个字符数组:

      char[] value;
        int count;

    value用来存储字符,而count表示数组中已有内容的大小,也就是长度。StringBuffer的主要操作有appendinsert等,这些操作都是在value上进行的,而不是像String一样每次操作都要new一个String,因此,StringBuffer在效率上要高于String。有了appendinsert等操作,value的大小就会改变,那么StringBuffer是如何操作容量的改变的呢? 首先StringBuffer有个继承自AbstractStringBuilder类的ensureCapacity的方法:

    public synchronized void ensureCapacity(int minimumCapacity) {
            if (minimumCapacity > value.length) {
                expandCapacity(minimumCapacity);
            }
        }

    StringBuffer对其进行了重写,直接调用父类的expandCapacity方法。这个方法用来保证value的长度大于给定的参数minimumCapacity,在父类的expandCapacity方法中这样操作:

    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);// 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
        }

    也就是说,value新的大小是max(value.length*2+2,minimumCapacity),上面代码中的第二个判断是为了防止newCapacity溢出。

    同时,StringBuffer还有一个直接设置count大小的函数setLength

    public synchronized void setLength(int newLength) {
            super.setLength(newLength);
        }

    函数直接调用父类的函数,父类函数如下:

    public void setLength(int newLength) {
            if (newLength < 0)
                throw new StringIndexOutOfBoundsException(newLength);
            ensureCapacityInternal(newLength);
    
            if (count < newLength) {
                for (; count < newLength; count++)
                    value[count] = '';
            } else {
                count = newLength;
            }
        }
    private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0)
                expandCapacity(minimumCapacity);
        }

    从代码中可以看出,如果newLength大于count,那么就会在后面添加''补充;如果小于count,就直接使count=newLength

    StringBuffer中的每一个appendinsert函数都会调用父类的函数:

    public synchronized StringBuffer append(Object obj) {
            super.append(String.valueOf(obj));
            return this;
        }
    public synchronized StringBuffer insert(int index, char[] str, int offset,
                                                int len)
        {
            super.insert(index, str, offset, len);
            return this;
        }

    而在父类中,这些函数都会首先保证value的大小够存储要添加的内容:

    public AbstractStringBuilder append(Object obj) {
            return append(String.valueOf(obj));
    }
    public AbstractStringBuilder append(String str) {
            if (str == null) str = "null";
            int len = str.length();
            ensureCapacityInternal(count + len);//容量修改
            str.getChars(0, len, value, count);
            count += len;//修改当前数组长度
            return this;
        }
    public AbstractStringBuilder insert(int index, char[] str, int offset,
                                            int len)
        {//将 str 数组参数的子数组的字符串表示形式插入此序列中。其中子数组从指定的 offset 开始,包含 len 个 char。子数组的字符将被插入 index 所指示的位置。此序列的长度将增加 len 个 char。 
            if ((index < 0) || (index > length()))
                throw new StringIndexOutOfBoundsException(index);
            if ((offset < 0) || (len < 0) || (offset > str.length - len))
                throw new StringIndexOutOfBoundsException(
                    "offset " + offset + ", len " + len + ", str.length "
                    + str.length);
            ensureCapacityInternal(count + len);
            System.arraycopy(value, index, value, index + len, count - index);
            System.arraycopy(str, offset, value, index, len);
            count += len;
            return this;
        }

    父类中通过ensureCapacityInternal的函数保证大小,函数如下:

    private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0)
                expandCapacity(minimumCapacity);
        }

    如果空间不够,最终也是调用expandCapacity方法。这样就保证了随着操作value的空间够用。 还有就是,StringBuffer可以通过toString方法转换为String

    public synchronized String toString() {
            return new String(value, 0, count);
        }

    总结:StringBuffer的扩容实际上就是新建了一个数组,将原来旧数组的内容复制到新数组,扩容机制根据当前数组长度的2+2和新增加字符串长度+原有数组长度进行比较,如果前者小于后者,那么扩容后的长度就是后者,如果前者大于后者那么扩容后的数组长度就是前者,每次append或者insert会再次进行比较.

    欢迎各位大神提问题及补充不足和警醒错误!

  • 相关阅读:
    splay区间模板-1331-序列终结者1
    splay单点模板-5203-BZOJ3224 普通平衡树
    线段树模板-1204-影子的宽度
    树状数组模板-1200-序列和
    YAML配置复杂集合类型
    webpack vue-router vue 打包上线一些列问题
    idea 注释模板
    JavaScript中reduce()方法
    ES6实用语法糖
    axios 备忘
  • 原文地址:https://www.cnblogs.com/lin-jing/p/7836640.html
Copyright © 2011-2022 走看看