JAVA源码阅读----String、StringBuffer、StringBuilder比较以及源码解读

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

总结

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的源码解读为例。

类型变量

首先我们看一下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;

方法

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

首先看看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()追加字符串方法

观察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容量时,保证其线程安全。

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的所以我们不需要继续关心下去。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容