谈谈JavaSE中的一些扩容机制-<StringBuffer,StringBuilder>

来源:互联网 发布:淘宝橙色cmyk 编辑:程序博客网 时间:2024/06/09 21:14

        StringBuffer,由名字可以看出,是一个String的缓冲区,也就是说一个类似于String的字符串缓冲区,和String不同的是,它可以被修改,而且是线程安全的。StringBuffer在任意时刻都有一个特定的字符串序列,不过这个序列和它的长度可以通过一些函数调用进行修改。它的结构层次如下图:


        StringBuffer是线程安全的,因此如果有几个线程同时操作StringBuffer,对它来说也只是一个操作序列,所有操作串行发生。每一个StringBuffer都有一个容量,如果内容的大小不超过容量,StringBuffer就不会分配更大容量的缓冲区;如果需要更大的容量,StringBuffer会自动增加容量。和StringBuffer类似的有StringBuilder,两者之间的操作相同,不过StringBuilder不是线程安全的。虽然如此,由于StringBuilder没有同步,所以它的速度更快一些。

    当发生与源序列有关的操作(如源序列中的追加或插入操作)时,该类只在执行此操作的字符串缓冲区上而不是在源上实现同步。

    每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

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

7.7.2.1 StringBuffer的初始容量

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

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

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

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

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

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

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

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

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

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

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

7.7.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操作.

7.7.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是如何操作容量的改变的呢?我们发现最后所有的方法,其实都是在AbstractStringBuilder类中的,以前总结过一次是以JDK1.7位蓝本总结的,这次以JDK1.8为蓝本总结,发现有不同,以此次为准;

//执行插入操作offset - 偏移量。 str - 一个 string。public AbstractStringBuilder insert(int offset, String str) {//offset指的是我们 要将字符串插入到的原字符串的位置,所以要注意索引越界异常        if ((offset < 0) || (offset > length()))            throw new StringIndexOutOfBoundsException(offset);        if (str == null)//注意这一点,如果插入的字符串是空字符串null.那么这里不会有nullPointException,而是掺入一个字符串”null”            str = "null";        int len = str.length();//获取要插入的字符串的长度        ensureCapacityInternal(count + len);//注意了:这是扩容扩容机制//arraycopy(Object src, int srcPos, Object dest, int destPos, int length)           从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。public static void arraycopy(Object src,                             int srcPos,                             Object dest,                             int destPos,                             int length)从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于 length 参数。源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos 到 destPos+length-1 位置。         System.arraycopy(value, offset, value, offset + len, count - offset);        str.getChars(value, offset);        count += len;        return this;    }
//测试null字符串的插入public static void main(String[] args) {StringBuffer sb = new StringBuffer();sb.append("西风多少恨,吹不散眉弯!");String str = null;sb.insert(2, str);System.out.println(sb);//西风null多少恨,吹不散眉弯!}
//添加public AbstractStringBuilder append(String str) {        if (str == null)//假如添加的是null字符串            return appendNull();//添加字符串”null”        int len = str.length();        ensureCapacityInternal(count + len);//扩容        str.getChars(0, len, value, count);//复制数组到指定数组,这就是为什么这些字符串是可变的,是通过扩容机制重新创建了数组,然后将原来的数组指向新数组,旧数组会被回收        count += len;        return this;}
private AbstractStringBuilder appendNull() {        int c = count;        ensureCapacityInternal(c + 4);        final char[] value = this.value;        value[c++] = 'n';        value[c++] = 'u';        value[c++] = 'l';        value[c++] = 'l';        count = c;        return this;    }
/**StringBuffer和StringBuilder除了通过插入和添加字符串动态扩容外,还可以通过动态设置容量的方法,来扩容,源码如下,我们观察和上面其实是一致的,这里不再分析:*/public void setLength(int newLength) {        if (newLength < 0)            throw new StringIndexOutOfBoundsException(newLength);        ensureCapacityInternal(newLength);        if (count < newLength) {            Arrays.fill(value, count, newLength, '\0');        }        count = newLength;    }

//重头戏,扩容机制//minimumCapacity = count +length 是原字符串长度+插入或添加的字符串长度,注意是字符串长度不是字符数组长度private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) {//假如我们的参数长度大于未扩容前的字符数组长度,那么复制原字符数组,到一个指定长度newCapacity(minimumCapacity)的新字符数组 value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } }
//对于字符串的长度,是返回的字符数组中有效的字符个数,而非字符长度@Override    public synchronized int length() {        return count;    }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//这是扩容中的新字符数组创建,扩容的本质是创建一个新数组,然后将原来的数据添加到新数组,这里是确定新数组的长度private int newCapacity(int minCapacity) {        // overflow-conscious code        int newCapacity = (value.length << 1) + 2;//长度=原长度*2+2        if (newCapacity - minCapacity < 0) {//假如新长度比原有字符串长度和新加字符串长度小,那么以后者为准            newCapacity = minCapacity;        }//这里是要返回的新字符数组的长度,如果长度小于等于0,或者newCapacity比MAX_ARRAY_SIZE大,那么返回hugeCapacity(minCapacity),否则返回长度newCapacity

(一般返回后者,返回前者的都不寻常)

return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }
private int hugeCapacity(int minCapacity) {//如果原字符串和新字符串长度超过String的最大值,那么就内存溢出异常        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow            throw new OutOfMemoryError();        }//否则根据原字符串与新字符串的长度与 MAX_ARRAY_SIZE的判定决定返回的值        return (minCapacity > MAX_ARRAY_SIZE)            ? minCapacity : MAX_ARRAY_SIZE;    }

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

    前面我们是对StringBuffer的扩容进行了讲解,StringBuilder和StringBuffer都是继承AbstractStringBuilder,所以扩容机制是一样的,这里不讲解了!
阅读全文
0 0
原创粉丝点击