Java StringBuffer & StringBuilder 源码分析

简介

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

String & StringBuffer & StringBuilder 类的定义

public final class String
    implements java.io.Serializable, Comparable<String>, 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。

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • Tip:笔者马上毕业了,准备开始 Java 的进阶学习计划。于是打算先从 String 类的源码分析入手,作为后面...
    石先阅读 11,971评论 16 58
  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 1,198评论 0 2
  • 突然觉得,一生只在一个城市生活,长大,上学,工作,变老,死去,是一件很可怕的事。 对啊,是可以去旅游,但是旅游和在...
    一丢丢DeiDeiBei阅读 138评论 1 0
  • Life isn’t always beautiful, but the struggles make you s...
    静静很快乐987阅读 133评论 0 0