在研究StringBuffer和StringBuilder时,两个继承了AbstractStringBuilder实现Serializable序列化和CharSequence,查看CharSequence时,发现里面只有5个方法。
其中最后一个方法是public default IntStream chars(),嗯。。。优秀
这里的default并非default权限修饰符,而是default关键字。它是Java8引入的关键字,也可称为Virtual extension methods——虚拟扩展方法。即在接口内部实现默认方法(也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。
那其他的4个方法也就是为不同的字符序列类提供了统一的规范了。
然后它是继承了AbstractStringBuffer,其中定义了数组char[] value;与String类不同的是它并没有设置为final,即可变数组。
两个构造方法:一个无参,一个动态创建容量构造
AbstractStringBuilder() {}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
然后是扩容的方法:
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
封装的扩容的逻辑如下:
1.当前最小容量为minimumCapacity,若小于等于0则返回。
2.若上一步大于0,则判断是否大于字符长度,若不是则返回。
3.若上一步是,则进行扩容。扩容是试探,以字符长度左移一位加上2作为基准newCapacity,若小于minimumCapacity,则将其赋给newCapacity,然后判断是否溢出。若是则定义为Integer的最大值。容量确定之后进行数组拷贝。
由此,我们可知,在创建StringBuffer和StringBuilder对象时,要尽量估算好字符串的初始值,避免其进行多次扩容带来的效率低下等问题。
下面这个方法看逻辑就知道是释放了多余的空间,减少内存空间的使用。
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
然后剩下的就是一些字符串基本的处理方法,这些在String里面也有,里面还有很多重载的append方法,返回值都是AbstractStringBuilder类型的,有兴趣可以看看,这里不便赘述。
再回过头来看StringBuilder和StringBuffer,StringBuffer相比于StringBuilder多了一个private transient char[] toStringCache,这里又出现了一个新的关键字transient,被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
StringBuffer/StringBuilder构造方法如下:
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
由此可知其默认构造大小为16。
剩下的代码都没啥说的了,只是StringBuffer相对于StringBuilder加了synchronized关键字修饰。
两者之间的区别?
继承关系?如何实现扩容?线程安全性?
继承关系如下所示:
实现扩容:由AbstractStringBuilder内部实现,当前最小长度大于16时,确定长度为2*length+2,创建新数组并将原数组拷贝过来。
线程安全性:StringBuffer除了构造方法之外所有方法都加了synchronized,几乎都是调用的父类的方法.,用synchronized关键字加锁,资源同步串行化处理,所以是线程安全的。StringBuilder是线程不安全的,但也正因为StringBuffer加了锁,占用了很多资源导致效率不高,StringBuilder效率较高。
StringBuffer和StringBuilder都继承自抽象类AbstractStringBuilder。
存储数据的字符数组也没有被final修饰,说明值可以改变,且构造出来的字符串还有空余位置拼接字符串,但是拼接下去肯定也有不够用的时候,这时候它们内部都提供了一个自动扩容机制,当发现长度不够的时候(默认长度是16),会自动进行扩容工作,扩展为原数组长度的2倍加2,创建一个新的数组,并将数组的数据复制到新数组,所以对于拼接字符串效率要比String要高。自动扩容机制是在抽象类中实现的。
线程安全性:StringBuffer效率低,线程安全,因为StringBuffer中很多方法都被 synchronized 修饰了,多线程访问时,线程安全,但是效率低下,因为它有加锁和释放锁的过程。StringBuilder效率高,但是线程是不安全的。