Java StringBuilder和StringBuffer源码分析

简介

StringBuilder 与 StringBuffer 是两个常用的操作字符串的类。大家都知道,
StringBuilder 是线程不安全的,而 StringBuffer
是线程不安全的。前者是JDK1.5加入的,后者在JDK1.0就有了。下面分析一下它们的内部实现。

阅读java源码可能是每一个java程序员的必修课,只有知其所以然,才能更好的使用java,写出更优美的程序,阅读java源码也为我们后面阅读java框架的源码打下了基础。阅读源代码其实就像再看一篇长篇推理小说一样,不能急于求成,需要慢慢品味才行。这一系列的文章,记录了我阅读源码的收获与思路,读者也可以借鉴一下,也仅仅是借鉴,澳门新浦京8455com,问渠那得清如许,绝知此事要躬行!要想真正的成为大神,还是需要自己亲身去阅读源码而不是看几篇分析源码的博客就可以的。

StringBuilder简介

继承关系

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

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

可以看到,两个类的继承关系是一模一样的。 Serializable
是可以序列化的标志。 CharSequence 接口包含了 charAt() 、 length() 、
subSequence() 、 toString() 等方法, String
类也实现了这个接口。这里的重点是抽象类 AbstractStringBuilder
,这个类封装了 StringBuilder 和 StringBuffer 大部分操作的实现。

总结

StringBuffer、StringBuilder的操作基本一致,只是StringBuffer在一些方法上加了锁,保证线程安全,他们都继承于AbstractStringBuilder。无参构造时,StringBuffer容量的初始大小是16,当向构造器中传入字符串时,其容量大小为字符串长度+16,同时也可以直接指定其容量大小。

而对于StringBuffer的扩容,首先是将容量大小扩大二倍后再加2,然后判断是否足够或则溢出,如果出现Int溢出则执行hugeCapacity()方法,将与Integer.MAX_VALUEMAX_ARRAY_SIZE = Integer.MAX_VALUE - 8进行比较,如果大于Integer.MAX_VALUE则抛出OutOfMemoryError错误,如果小于MAX_ARRAY_SIZE则赋值为MAX_ARRAY_SIZE,如果小于Integer.MAX_VALUE,大于MAX_ARRAY_SIZE则返回扩容后的值。
如果没有溢出,并且扩容后的容量足够,那么就将容量设置为扩容后的容量,如果不够,且没有溢出,则直接将容量大小赋值为所需容量。

StringBuffer还支持手动扩容,在创建StringBuffer的对象后,还可以使用ensureCapacity()方法将容量扩到指定大小。如果对于内存利用率要求比较高的情况,还可以利用StringBuffer的trimToSize()方法对
StringBuffer的容量进行缩减,释放出没有被使用的存储空间。

在阅读完String、StringBuffer、StringBuilder源码后,我们可以发现之所以StringBuffer、StringBuilder在对字符串进行例如赋值,追加,替换等操作时会比String高效的多,就是因为String每次操作后会创建一个新的Srting对象来存储结果,而StringBuffer、StringBuilder无需重新创建对象。三者对于字符串的操作本质上都是在对存储字符串的字符数组value操作。

因为StringBuffer和StringBuilder的操作基本一样,所以这里以StringBuffer的源码解读为例。

StringBuilder是常用操作字符串的类,它是线程不安全的。它和String类的不同在于,String类是不可改变的,而StringBuilder可以改变

AbstractStringBuilder

类型变量

首先我们看一下StringBuffer有哪些变量:
StringBuffer独有的变量:

 /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

这里序列号serialVersionUID是为了对象序列化可以忽略,剩下的就只有一个char数组toStringCache。代码中的注释也说的很清楚了,这个变量是用于缓存当前存储的字符串,当变量改变时清空。这里的改变包括很多情况,总结来说就是两点:当存储的字符串值改变或者字符串的长度改变时,toStringCache会被清空。
StringBuffer继承了AbstractStringBuilder,所以它还有下面的变量:

 /**
     * 存储字符串的字符数组
     * The value is used for character storage.
     */
    char[] value;

    /**
     * 当前存储的字符串的长度
     * The count is the number of characters used.
     */
    int count;

类头部

变量及构造方法

char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

AbstractStringBuilder 内部用一个 char[]
数组保存字符串,可以在构造的时候指定初始容量方法。

方法

这里我们主要关注StringBuffer的构造方法和与扩容相关的方法。

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

扩容

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > 0)
        ensureCapacityInternal(minimumCapacity);
}
 private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}
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);
}

扩容的方法最终是由 expandCapacity() 实现的,在这个方法中首先把容量扩大为
原来的容量加2 ,如果此时仍小于指定的容量,那么就把新的容量设为
minimumCapacity 。然后判断是否溢出,如果溢出了,把容量设为
Integer.MAX_VALUE 。最后把 value 值进行拷贝, 这显然是耗时操作

首先看看StringBuffer的构造方法:
/**
     *创建一个没有存储任何字符的字符串变量,并且它的初始容量为16
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);
    }

    /**
     *创建一个指定容量大小的没有存储任何字符的字符串变量。
     * Constructs a string buffer with no characters in it and
     * the specified initial capacity.
     *
     * @param      capacity  the initial capacity.
     * @exception  NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);
    }

    /**
     *根据String,获得一个StringBuilder,这里可以用于String转换为StringBuilder.
     * Constructs a string buffer initialized to the contents of the
     * specified string. The initial capacity of the string buffer is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

    /**
     *根据字符序列获得一个StringBuilder。
     * Constructs a string buffer that contains the same characters
     * as the specified {@code CharSequence}. The initial capacity of
     * the string buffer is {@code 16} plus the length of the
     * {@code CharSequence} argument.
     * <p>
     * If the length of the specified {@code CharSequence} is
     * less than or equal to zero, then an empty buffer of capacity
     * {@code 16} is returned.
     *
     * @param      seq   the sequence to copy.
     * @since 1.5
     */
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

这里可以总结出,StringBuffer的出示容量是16,我们也可以自定义创建指定初始容量大小的StringBuffer。而当是String或者CharSequence转换为StringBuffer时,StringBuffer的初始容量大小=输入的字符串长度+16

变量及构造方法

append()方法

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

append()
是最常用的方法,它有很多形式的重载。上面是其中一种,用于追加字符串。如果
str 是 null ,则会调用 appendNull() 方法。这个方法其实是追加了 ‘n’ 、 ‘u’
、 ‘l’ 、 ‘l’ 这几个字符。如果不是 null ,则首先扩容,然后调用 String 的
getChars() 方法将 str 追加到 value 末尾。最后返回对象本身,所以 append()
可以连续调用。

append()追加字符串方法

观察StringBuffer的所有append()方法我们可以发现,其append()的实现是如下三个步骤并且是加了锁的:

1.令toStringCache = null。
2.super.append(), 调父类相应的append()方法。
3.返回结果。

令toStringCache=null,与前面toStringCache注释相对应。我们接下来看看super.append()代码,即AbstractStringBuilder的append()方法。

 public AbstractStringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    /**
     * Appends the specified string to this character sequence.
     * <p>
     * The characters of the {@code String} argument are appended, in
     * order, increasing the length of this sequence by the length of the
     * argument. If {@code str} is {@code null}, then the four
     * characters {@code "null"} are appended.
     * <p>
     * Let <i>n</i> be the length of this character sequence just prior to
     * execution of the {@code append} method. Then the character at
     * index <i>k</i> in the new character sequence is equal to the character
     * at index <i>k</i> in the old character sequence, if <i>k</i> is less
     * than <i>n</i>; otherwise, it is equal to the character at index
     * <i>k-n</i> in the argument {@code str}.
     *
     * @param   str   a string.
     * @return  a reference to this object.
     */
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    // Documentation in subclasses because of synchro difference
    public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    /**
     * @since 1.8
     */
    AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);
        asb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    // Documentation in subclasses because of synchro difference
    @Override
    public AbstractStringBuilder append(CharSequence s) {
        if (s == null)
            return appendNull();
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);

        return this.append(s, 0, s.length());
    }

从上面的代码可以看出,在AbstractStringBuilder进行追加操作时,主要分为一下几个步骤:

1.判断要追加的字符串是否为空,如果为空那么就执行appendNull()后返。
2.不为空,就执行ensureCapacityInternal()方法。
3.最后执行getChars()方法。

那么我们接下来看看appendNull()、ensureCapacityInternal()、getChars()方法的源代码:
appendNull():

private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

这里可以看到,当我们追加的字符串为空时,StringBuffer会在末尾直接追加”null”;

ensureCapacityInternal():

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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;
        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;
    }

可以看出,ensureCapacityInternal()的主要作用就是检测当前的容量是否足够容纳追加后的字符串,如果不能那么就先将容量扩充到原来的2倍,再加上2,如果还不够,那么直接将容量扩充到所需容量大小,如果时大容量即2倍之后会造成溢出的情况且小于Integer.MAX_VALUE

  • 8;,那么就将容量设定为Integer.MAX_VALUE –
    8。容量最大值为int的最大值。而对于ensureCapacity()方法,在StringBuffer中重写了的,这里我想StringBuffer重写的原因时为了在手动扩充StringBuffer容量时,保证其线程安全。
// 这两个变量都继承自父类AbstractStringBuilder
char[] value;
int count;

// 默认构造方法,将value初始化为容量是16的数组
public StringBuilder() {
  super(16);
}
public StringBuilder(int capacity) {
  super(capacity);
}
public StringBuilder(String str) {
  // 初始化为字符串长度+16
  super(str.length() + 16);
  append(str);
}
public StringBuilder(CharSequence seq) {
  this(seq.length() + 16);
  append(seq);
}

StringBuilder

AbstractStringBuilder 已经实现了大部分需要的方法, StringBuilder 和
StringBuffer 只需要调用即可。下面来看看 StringBuilder 的实现。

ensureCapacity()手动扩容方法
@Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }

getChars():

public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
    {
        if (srcBegin < 0)
            throw new StringIndexOutOfBoundsException(srcBegin);
        if ((srcEnd < 0) || (srcEnd > count))
            throw new StringIndexOutOfBoundsException(srcEnd);
        if (srcBegin > srcEnd)
            throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

在进行判空、判容之后,就开始进行真正的字符追加操作了,这里在判断参数合理性之后,调用了底层的内存拷贝方法arraycopy(),这个方法时native的所以我们不需要继续关心下去。

扩容

构造器

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);
}

可以看出, StringBuilder 默认的容量大小为16
。当然也可以指定初始容量,或者以一个已有的字符序列给 StringBuilder
对象赋初始值。

扩容方法最终是由expandCapacity()实现的,这个方法中,首先把容量扩大为原来容量的2倍+2,
如果此时仍小于指定的容量,那么就把新的容量设为minimumCapacity,然后判断是否溢出,如果溢出,把容量设为Integer.MAX_VALUE。最后把value值进行拷贝。但这明显是耗时的操作。

append()方法

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

append() 的重载方法很多,这里随便列举了两个。显然,这里是直接调用的父类
AbstractStringBuilder 中的方法。

public void ensureCapacity(int minimumCapacity) {
  if (minimumCapacity - value.length > 0)
    expandCapacity(minimumCapacity);
}
// 真正的扩容在这个方法
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);
}

toString()

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

toString() 方法返回了一个新的 String 对象,与原来的对象不共享内存。其实
AbstractStringBuilder 中的 subString() 方法也是如此。

append()方法

SringBuffer

StiringBuffer 跟 StringBuilder 类似,只不过为了实现同步,很多方法使用l
Synchronized 修饰,如下面的方法:

public synchronized int length() {
        return count;
}
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
public synchronized void setLength(int newLength) {
    toStringCache = null;
    super.setLength(newLength);
}

可以看到,方法前面确实加了 Synchronized 。

另外,在上面的 append() 以及 setLength() 方法里面还有个变量
toStringCache 。这个变量是用于最近一次 toString()
方法的缓存,任何时候只要 StringBuffer 被修改了这个变量会被赋值为 null 。
StringBuffer 的 toString 如下:

public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

在这个方法中,如果 toStringCache 为 null 则先缓存。最终返回的 String
对象有点不同,这个构造方法还有个参数 true 。找到 String 的源码看一下:

 String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

原来这个构造方法构造出来的 String 对象并没有实际复制字符串,只是把 value
指向了构造参数,这是为了节省复制元素的时间。不过这个构造器是具有包访问权限,一般情况下是不能调用的。

append()方法是最常用的方法,有多种重载的形式,用于追加字符串。如果参数是null,则会调用appendNull()方法。这个方法其实是追加了’n’、’u’、’l’、’l’这几个字符。如果不是null,则首先扩容,然后调用String的getChars()方法将str追加到value末尾。最后返回对象本身,所以append()可以连续调用。

总结

  • StringBuilder 和 StringBuffer
    都是可变字符串,前者线程不安全,后者线程安全。
  • StringBuilder 和 StringBuffer 的大部分方法均调用父类
    AbstractStringBuilder
    的实现。其扩容机制首先是把容量变为原来容量的2倍加2。最大容量是
    Integer.MAX_VALUE ,也就是 0x7fffffff 。
  • StringBuilder 和 StringBuffer
    的默认容量都是16,最好预先估计好字符串的大小避免扩容带来的时间消耗。
public AbstractStringBuilder append(StringBuffer sb) {
  if (sb == null) {
    return append("null");
  }
  int len = sb.length();
  ensureCapacityInternal(count + len);
  sb.getChars(0, len, value, count);
  count += len;
  return this;
}

toString()方法

public String toString() {
  // 创建一份拷贝而不是共享value数组
  return new String(value, 0, count);
}

StringBuffer

StringBuffer跟StringBuilder类似,只不过前者实现了同步,很多方法使用sychronized修饰,如下所示:

public synchronized int capacity() {
  return value.length;
}
public synchronized int length() {
  return count;
}

总结

  • StringBuilder是JDK1.5引进的,而StringBuffer是在1.0就有了
  • StringBuilder和StringBuffer都是可变字符串,可以通过append或者insert方法修改串内容。
  • StringBuffer是线程安全而StringBuilder不是。在多线程情况下优先使用StringBuffer,而其他情况推荐使用StringBuilder,因为它更快。
  • StringBuffer和StringBuilder都继承于AbstractStringBuilder类,AbstractStringBuilder类主要实现了扩容,append、insert方法,这两者的相关方法都是调用父类的方法。
  • StringBuilder和StringBuffer的初始容量都是16;程序员尽量手动设置初始值,以避免多次扩容所带来的性能问题。
  • StringBuilder和StringBuffer的扩容机制都是先将当前数组容量扩展为原来数组容量的2倍加2,如果这个容量仍小于预定的最小值,那么就将新容量定为这个最小值,最后判断是否溢出,如果溢出则定义为整型的最大值。
You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图