今天分析的两个类是:StringBuffer 和 StringBuilder。开篇前,先看看它们的继承层次:html
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类型,不能再派生子类。java
(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,详细参见源码)app
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的不可变性。ui
String(char[] value, boolean share) { // assert share : "unshared not supported"; this.value = value;// 没有真正复制,只是赋值引用 }
对比一下,下面是实际复制了数组元素的: this
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。