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

        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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值