Java StringBuffer & StringBuilder 源码分析

简介

总所周知,StringBuffer 是线程安全的,是 JDK 1.0 加入的;StringBuilder 是线程不安全的,是 JDK 1.5 加入的。

String & StringBuffer & StringBuilder 类的定义

public final class String
    implements java.io.Serializable, Comparable, CharSequence {

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

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

①、三者都实现了 CharSequence 接口。也就是说,CharSequence 定义了字符串操作的接口,三者完成具体的实现;
②、Serializable 是可以序列化的标志;
③、后两者继承了 AbstractStringBuilder 类,而这个类封装了 StringBuilder 和
StringBuffer 大部分操作的实现。

AbstractStringBuilder

AbstractStringBuilder 是 StringBuffer & StringBuilder 两者的父类。
相同点:两者的构造函数都是调用了其父类的构造函数;
不同点:StringBuffer 大部分重写了其父类的方法,并加同步锁(synchronized),速度较慢;而 StringBuilder 则是直接调用其父类的方法,速度更快。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    int count;  // value 数组中实际上存放的字符数目
    AbstractStringBuilder() {}
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
    public int length() {
        return count; //返回的是实际存放的字符数目
    }
    public int capacity() {
        return value.length;  //返回的是内置字符数组的长度
    }

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

    private void ensureCapacityInternal(int minimumCapacity) {
        //  如果需要扩展到的容量 > 当前字符数组长度,那么就正常扩容
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }
    //用于保证字符数组长度的方法,ArrayList 中也使用这种动态扩容的思想
    void expandCapacity(int minimumCapacity) {
        // 初始化新的容量大小 = 当前字符串长度的 2 倍加 2
        int newCapacity = value.length * 2 + 2;
        // 新容量大小 < 传进来的最小容量,就用最小的容量作为新数组的容量
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        // 如果新的容量和最小容量都小于0
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            // 把容量设为Integer.MAX_VALUE
            newCapacity = Integer.MAX_VALUE;
        }
        // 创建容量大小为 newCapacity 的新数组
        value = Arrays.copyOf(value, newCapacity);
    }
    // 去除 value 字符数组中所有为空的元素,使其 count = value.length
    public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }
    ...
}

属性

由 AbstractStringBuilder 类可知,StringBuffer & StringBuilder 父类中封装的字符数组没有 final 修饰,也就说明了 StringBuffer & StringBuilder 中的字符数组可以被不断修改。而 String 则相反。

方法

由于 StringBuffer & StringBuilder 大部分的方法都是直接调用或者重写父类的方法,所以下面我们着重分析其父类 AbstractStringBuilder 的方法

构造方法

StringBuffer & StringBuilder 两者的构造方法是一样的,都需要调用父类的构造方法。存储的字符数组,也是在父类中定义的。

    public StringBuilder() {
        super(16);   // 默认的容量的大小是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);
    }

append()

    public AbstractStringBuilder append(String str) {
        // 如果传入的参数为 null,则直接调用 appendNull() 方法在后面追加 'n'、'u'、'l'、'l' 四个字符。
        if (str == null)
            return appendNull();
        int len = str.length();
        // 首先,调用动态扩容方法
        ensureCapacityInternal(count + len);
        // 将 str 从 0 到 len-1 位置上的字符复制到字符数组 value 中,并从 count 
处开始存放(将 str 追加到 value 末尾)
        str.getChars(0, len, value, count);
        count += len; 
        // 返回对象本身,使其 append() 可以连续调用
        return this;
    }

delete()

删除 [start,end) 区域的字符(包括 start,不包括 end)

    public AbstractStringBuilder delete(int start, int end) {
        // 健壮性的检查
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        // 需要删除的长度
        int len = end - start;
        if (len > 0) {
            // 核心语句:将下标 [start,end] 区域的位置,用 [end,count] 区域的字符进行覆盖。
            System.arraycopy(value, start+len, value, start, count-end);
            // 并更新 count,这样就只能输出[0,count] 区域的字符。
            count -= len;
        }
        return this;
    }

delete 的底层操作,并没有真正的删除字符,而是把后面的字符进行前移,从而覆盖。其中 deleteCharAt 也是一样的原理。

insert()

在 offset 位置插入字符串 str

    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        // 调用扩容方法
        ensureCapacityInternal(count + len);
        // 将 [offset,count] 区域字符串向后移动 len 个位置,为插入字符串留出空间
        System.arraycopy(value, offset, value, offset + len, count - offset);
        // 将 str 复制到 value 字符数组中 offset 之后的位置
        str.getChars(value, offset);
        // 更新当前对象中记录的长度
        count += len;
        return this;
    }

该 insert 方法有很多重载,但是本质上都离不开我们上述介绍的这个方法。

replace()

将 [start,end) 区域的字符串替换成 str

    public AbstractStringBuilder replace(int start, int end, String str) {
        // 健壮性的检查
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (start > count)
            throw new StringIndexOutOfBoundsException("start > length()");
        if (start > end)
            throw new StringIndexOutOfBoundsException("start > end");
        if (end > count)
            end = count;
        // 获取需要添加的字符串的长度
        int len = str.length();
        // 计算新字符串的长度
        int newCount = count + len - (end - start);
        // 调用扩容方法
        ensureCapacityInternal(newCount);
        // 将从 end 开始到最后的字符,向后移动到 start+len 的位置
        System.arraycopy(value, end, value, start + len, count - end);
        // 将 str 复制到 value 字符数组中 start 后面
        str.getChars(value, start);
        // 更新字符串长度
        count = newCount;
        return this;
    }

toString()

返回了一个新的 String 对象,与原来的对象不共享内存

    public String toString() {
        return new String(value, 0, count);
    }

扩展

①、在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高;
②、StringBuffer & StringBuilder 对象的 append 效率要高于String 对象的"+"连接操作;
③、在不考虑线程安全的情况下,首选 StringBuilder 类,效率最高。否则选择 StringBuffer。

你可能感兴趣的:(Java StringBuffer & StringBuilder 源码分析)