StringBuilder类和StringBuffer类的使用与解析

目录

一、使用

二、实现原理


一、使用

        StringBuilder类和StringBuffer类的功能差不多,且都是调用AbstractStringBuilder抽象类的方法来实现的,二者最主要的不同在于:StringBuffer类解决了线程安全问题(成员方法都加了synchronized关键字),所以,实际编码时应使用哪个类都是从线程安全及其影响(包括性能影响)来判断的,不必死记硬背。

        为什么当有很多字符串拼接时,用StringBuilder类或StringBuffer类会比“+”的性能好呢?因为每加一个字符串都要新产生一个String实例,且每加完一次就会产生一个新的String实例并返回,原有的实例被丢弃,这样就会产生大量String实例,JVM产生和销毁实例都是要消耗时间和资源的;如果我们能预估拼接完后的字符串的长度,再利用StringBuilder或StringBuffer的构造方法(如:StringBuffer(int capacity))一次性地分配合适的空间,append时就只需将要拼接的字符存入原来的byte数组了,不会产生过多的无用实例,所以性能会好些。

        同样,通过“+”拼接字符串时,也应考虑线程安全的问题。

二、实现原理

        由“String类的使用与解析”这篇博客可知,若字符串全是拉丁字符,则byte数组的每一个元素存储一个字符, coder值为0;若字符串是Unicode字符,则byte数组的每两个元素存储一个字符,且下标较小的存储低8位,较大的存储高8位,coder值1。

        如果我们向字符串“This is a test”后追加字符串“实例”,其存储形式又是怎样的呢?若为原字符串的value属性分配的空间不够了又怎么办呢?

        下面我们用一个简单的实例来剖析StringBuilder类和StringBuffer类的存储机制和扩容机制:

StringBuffer buff = new StringBuffer();
buff.append("This is a test");
buff.append("实例");

1、实例化StringBuffer       

        上面的代码首先用默认的构造方法产生一个StringBuffer实例,并声明一个buff变量指向该实例。

public StringBuffer() {
        super(16);
}

AbstractStringBuilder(int capacity) {
        if (COMPACT_STRINGS) {
            value = new byte[capacity];
            coder = LATIN1;
        } else {
            value = StringUTF16.newBytesFor(capacity);
            coder = UTF16;
        }

}

        StringBuffer构造方法调用其父类的构造方法来处理,并传入初始容量值16,COMPACT_STRINGS的值默认是true(看String类源码),所以就直接生成了容量为16的byte数组,coder也标记为拉丁(值为0)。此时value的值如下所示:

                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

2、添加"This is a test"字符串

        此时会调用AbstractStringBuilder类的append方法来处理,核心代码如下:  

        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;

        字符串长度的计算用“value.length >> coder()”实现,即:如果是拉丁字符,coder值为0,返回的就是value数组的长度,若是Unicode字符(UTF-16),coder值为1,返回的就是value.length/2(左移一位相当于乘以2,右移一位相当于除以2)的值。

        ensureCapacityInternal方法用于处理字符串的容量,核心代码如下:

        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }

        由于传入的是count(原串字符个数:0)+len(待拼接字符串的字符个数:14),即14,此时的oldCapacity等于16,所以在此不需扩容。

        putStringAt方法的核心代码如下:

        if (getCoder() != str.coder()) {
            inflate();
        }
        str.getBytes(value, off, index, coder, end - off);

        由于原串和要拼接的串都是拉丁字符,所以直接调用getBytes方法将待拼接的字符复制到原串的value数组中。此时的value值如下:

        [84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115, 116, 0, 0]

3、添加"实例"字符串

        由于拼接后的字符个数(16)与原串的容量(16)相等,所以先不会执行“value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);”扩容。由于原串是拉丁字符,被拼接串是UTF-16,所以执行putStringAt方法时会调用inflate方法,核心代码如下:

private void inflate() {
        ......
        byte[] buf = StringUTF16.newBytesFor(value.length);
        StringLatin1.inflate(value, 0, buf, 0, count);
        this.value = buf;
        this.coder = UTF16;
}

public static byte[] newBytesFor(int len) {
        ......
        return new byte[len << 1];
}

public static void inflate(byte[] src, int srcOff, byte[] dst, int dstOff, int len) {
        ......
        for (int i = 0; i < len; i++) {
            putChar(dst, dstOff++, src[srcOff++] & 0xff);
        }

}

static void putChar(byte[] val, int index, int c) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        val[index++] = (byte)(c >> HI_BYTE_SHIFT);
        val[index]   = (byte)(c >> LO_BYTE_SHIFT);
}

       newBytesFor方法产生一个新的byte数组,其容量是原字符串容量的2倍,putChar方法将原串中的字符复制到新生成的byte数组中,val[index++]存放低8位,val[index]存放高8位(此时的C是拉丁字符的编码,只需8位存储,右移8位后,变成0),所以处理完毕后value的值如下:

        [84, 0, 104, 0, 105, 0, 115, 0, 32, 0, 105, 0, 115, 0, 32, 0, 97, 0, 32, 0, 116, 0, 101, 0, 115, 0, 116, 0, -98, 91, -117, 79]

        从第0~27个元素存的“This is a test”,28~31存的是“实例”。

4、执行“value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);”进行扩容:

        newLength > 2 * oldLength + 2个字符的长度:扩容后的长度是newlength

        newLength < 2 * oldLength + 2个字符的长度:扩容后的长度是oldLength + 2个字符的长度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值