StringBuffer和StringBuilder源码分析

    今天分析的两个类是:StringBuffer 和 StringBuilder。开篇前,先看看它们的继承层次:

public final class StringBuffer extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {...}

public final class StringBuilder extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {...}

    都继承了 AbstractStringBuilder ,实现了 Serializable 和 CharSequence 接口。final类型,不能再派生子类。

一.成员变量

    (1) char[] value;// 底层都是用字符数组char[]实现,存储字符串,默认的大小为16。在父类 AbstractStringBuilder 中定义的。String的value数组使用final修饰,不能变动,StringBuffer和StringBuilder的value数组没有final修饰,是可变的

    关于数组的大小,默认的初始化容量是16。这个数有木有想起了Map的实现子类的初始容量。假如初始化的时候,传入字符串,则最终的容量将是 (传入字符串的长度 + 16) 。

    (2) private transient char[] toStringCache;// StringBuffer特有,缓存toString最后一次返回的值。

    如果多次连续调用toString方法的时候由于这个字段的缓存就可以少了Arrays.copyOfRange的操作(每次调用其他的修改StringBuffer对象的方法时,这些方法的第一步都会先将toStringCache设置为null,详细参见源码)

    StringBuilder.toString() 

     public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
     }

    StringBuffer.toString()    

     public synchronized String toString() {
        if (toStringCache == null) {// toStringCache为空,第一次操作
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);// 使用缓存的toStringCache,实际只传递了引用,没有复制操作
     }

    String 提供了一个保护类型的构造方法。目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不同才能进行重载。那么,第二个区别就是具体的方法实现不同。这里直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。作用的话,肯定是性能好一点。假如把该方法改为public,而不是protected的话,对外开放访问,就可以通过修改数组的引用来破坏String的不可变性。

    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;// 没有真正复制,只是赋值引用
    }

    对比一下,下面是实际复制了数组元素的:    

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);// 用到Arrays的copyOf方法将value中的内容逐一复制到String当中
    }

 

二.成员方法

    两者方法最大的区别是:StringBuffer是线程安全的,StringBuilder是非线程安全的。实现是StringBuffer在和StringBuilder相同的方法上加了 synchronized 修饰。

    StringBuffer.append(String)        

    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

    StringBuilder.append(String)    

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

 

三.底层存储的扩容机制

    通过分析源码,发现每次需要扩容的都是按照 "以前容量*2+2" 进行扩容,如果扩容之后仍不满足所需容量,则直接扩容到所需容量。

    对外通过 ensureCapacity(size) 来主动扩容。    

    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }

    AbstractStringBuilder.ensureCapacityInternal(int) private修饰,供类内部调用。newCapacity是在JDK1.8中拆分出来的方法,之前扩容都是在一个方法里面操作完成的 - expandCapacity    

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,newCapacity(minimumCapacity));
        }
    }
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;// 以前的容量*2 + 2
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity) : newCapacity;
    }
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE;
    }

    特意去对比了一下JDK1.7的源码:

        AbstractStringBuilder.ensureCapacity(int)

            AbstractStringBuilder.ensureCapacityInternal(int)

               AbstractStringBuilder.expandCapacity(int)

        JDK1.8的源码:

        AbstractStringBuilder.ensureCapacity(int)

            AbstractStringBuilder.ensureCapacityInternal(int)

                AbstractStringBuilder.newCapacity(int)

                    AbstractStringBuilder.hugeCapacity(int)

    通过对比发现,JDK8把之前在一个方法里面做的操作拆分成了两个方法,看源码的时候,更容易理解。

 

四.两者对比总结

    1.相同点

        (1)继承层次相同,都继承了 AbstractStringBuilder ,实现了 Serializable 和 CharSequence 接口;

        (2)底层都是用字符数组实现,字符串都是可变的,区别于String;

        (3)初始容量都是16,扩容机制都是"以前容量*2+2"

    2.不同点

        (1)StringBuilder不是线程安全的,StringBuffer是线程安全的(方法上多了synchronized修饰);

        (2)StringBuffer比StringBuilder多了一个toStringCache字段,用来在toString方法中进行缓存;

        (3)StringBuilder没有加同步,在不会出现线程安全问题的情况下,性能上StringBuilder应该要高于StringBuffer

 

五.扩展

    今晚看源码的时候,发现上面的数组容量有个最大值,很好奇,点进去看一下:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;// Some VMs reserve some header words in an array.

    已有的注释意思是:有些虚拟机在数组中保留了一些头信息。避免内存溢出。

    MAX_VALUE是0x7fffffff,即 2^31 = 2,147,483,648 。那为啥最大数组的大小是 2^31 减掉 8 呢?8这个数字刚好是一个字节里面比特的数量,让人联想翩翩。

    然后去gg了一下:在StackOverflow上,有个已经解答的问题,Why the maximum array size of ArrayList is Integer.MAX_VALUE - 8?

    回答参考了developerworks的论文:Java Memory management

    原因是:数组需要 8 byte 来存储自己的大小数量,所以最大数组定义为 Integer.MAX_VALUE - 8。

 

转载于:https://my.oschina.net/javamaster/blog/1933812

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值