今天分析的两个类是:StringBuffer 和 StringBuilder。开篇前,先看看它们的继承层次:
都继承了 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()
StringBuffer.toString()
String 提供了一个保护类型的构造方法。目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不同才能进行重载。那么,第二个区别就是具体的方法实现不同。这里直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。作用的话,肯定是性能好一点。假如把该方法改为public,而不是protected的话,对外开放访问,就可以通过修改数组的引用来破坏String的不可变性。
对比一下,下面是实际复制了数组元素的:
二.成员方法
两者方法最大的区别是:StringBuffer是线程安全的,StringBuilder是非线程安全的。实现是StringBuffer在和StringBuilder相同的方法上加了 synchronized 修饰。
StringBuffer.append(String)
StringBuilder.append(String)
三.底层存储的扩容机制
通过分析源码,发现每次需要扩容的都是按照 "以前容量*2+2" 进行扩容,如果扩容之后仍不满足所需容量,则直接扩容到所需容量。 对外通过 ensureCapacity(size) 来主动扩容。
AbstractStringBuilder.ensureCapacityInternal(int) private修饰,供类内部调用。newCapacity是在JDK1.8中拆分出来的方法,之前扩容都是在一个方法里面操作完成的 - expandCapacity
特意去对比了一下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
五.扩展
今晚看源码的时候,发现上面的数组容量有个最大值,很好奇,点进去看一下:
已有的注释意思是:有些虚拟机在数组中保留了一些头信息。避免内存溢出。 MAXVALUE是0x7fffffff,即 2^31 = 2,147,483,648 。那为啥最大数组的大小是 2^31 减掉 8 呢?8这个数字刚好是一个字节里面比特的数量,让人联想翩翩。 然后去gg了一下:在StackOverflow上,有个已经解答的问题,Why the maximum array size of ArrayList is Integer.MAXVALUE - 8? 回答参考了developerworks的论文:Java Memory management 原因是:数组需要 8 byte 来存储自己的大小数量,所以最大数组定义为 Integer.MAX_VALUE - 8。