StringBuilder以及append()方法源码解析

一、简述

StringBuilder是一个可变的字符序列,提供和StringBuffer类似的API,但是在多线程下不能保证安全,为什么是可变的以及不安全,这个后面会说,面试经常问及原理
它用于单个线程正在使用的字符缓冲序列。效率要比StringBuffer更快。

二、可变原理以及和String对比

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    
    char[] value;
    
    //字符序列已使用容量
    int count;

    AbstractStringBuilder() {
    }

StringBuilder其实是AbstractStringBuilder抽象类的子类,它的父类里定义了两个变量,一个char类型的数组value,以及整数型的count。
value用来存放字符序列,且并没有被fianl修饰,所以它的字符是可变的。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

它与String类的区别就在于String类里的value被final修饰,所以不可变

三、append(String str)方法源码解析

StringBuilder提供了很多append重载方法,参数类型丰富,实现原理大致相同,这里以常用的String参数作解析

@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

实际是调用了父类AbstractStringBuilder的append(str)方法;

//入参str就是要追加的字符串
public AbstractStringBuilder append(String str) {
        //如果是null就追加个空内容
        if (str == null)
            return appendNull();
        //不是null,首先获取追加字符串的长度
        int len = str.length();
        //紧接的方法是保证char数组的容量足够去追加新的字符串,这个方法在3.1里分析
        ensureCapacityInternal(count + len);
        //然后调用getChars()方法,这个是最终追加字符串的方法,在3.2里分析
        str.getChars(0, len, value, count);
        //最后将字符序列数组已用大小加上追加字符串的长度大小
        count += len;
        return this;
    }

最后一步count += len;可以看出StringBuilder是线程不安全的,因为此处是非原子性操作。count值可能没及时更新到主内存就被其他线程读取使用,导致安全性问题。

3.1 ensureCapacityInternal()方法入参是value数组的初始容量加上待追加字符串的长度,定义为最小容量。也就是追加字符串后value数组的最小容量。

private void ensureCapacityInternal(int minimumCapacity) {
        // 这里判断最小容量是不是比初始容量还要大,初始容量是16。逻辑成立则调用newCapacity()方法进行扩展得到新的数组容量
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
private int newCapacity(int minCapacity) {
        // 将初始数组容量扩容到原来的两倍并加2,也就是34
        int newCapacity = (value.length << 1) + 2;
        //如果扩容后的新容量还是比最低容量小 则将新容量改为最低容量,以此满足最低需求
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        //返回最终满足扩容的新容量大小,
        //如果新容量比最大的数组容量(3的21次方-8)还大就返回巨型容量,否则返回自己
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

//获取巨型容量
    private int hugeCapacity(int minCapacity) {
    //如果最低容量比数组的极限容量(包含数组元数据,长度描述所占的8bytes,即:3的21次方)还大则抛出内存不足的异常
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        //如果最低容量比数组最大容量(不包含数组元数据,长度描述所占的8bytes即:3的21次方-8)还大则返回最低容量大小,否则返回数组最大容量
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

3.2 上面3.1调整好字符序列的容量后,开始调用getChars()方法,完成新增字符串的追加。

//srcBegin 0 表示需要待追加的字符的起始位置
//srcEnd str.length 表示需要待追加的字符的结束位置
//dst[] 原字符序列 目标源,即AbstractStringBuilder已存在的字符序列
//dstBegin 原字符序列已使用容量
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        //这里的value是str的数组序列,也就是待追加字符串。并不是AbstractStringBuilder的字符序列。
        //该方法的流程就是将待追加的字符整个追加到原字符序列的末尾。如何确定末尾位置,根据dstBegin。
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

四、总结下:StringBuilder为何可变因为,字符存放的数组value没有被fianl修饰,为何不安全,因append()方法里有非原子性操作,即count += len。append()方法主要做了三件事,step1:根据情况扩容,step2:复制待追加字符到字符序列数组里,step3:更新字符序列已使用容量count

--------- 文章如有问题,请下方回复指出,感谢查阅,祝每天进步一点点 😁 -------------

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