从源码分析StringBuilder和StringBuffer是如何做到长度可变的以及它们两者的区别

目录

1正文

1.1以StringBuilder类为例

1.1.1成员属性

1.1.2四种构造方法

1.1.3详细分析的方法

1.2StringBuffer类,它与builder继承自同一超类AbstractStringBuilder

1.2.1成员属性

1.2.2构造方法

1.2.3重点分析方法(与上述Builder中的方法的同名方法和toString方法)

2总结(即回答标题问题)

2.1StringBuilder和StringBuffer是如何做到长度可变:

2.2两者的区别


1正文

1.1以StringBuilder类为例

1.1.1成员属性

value没有被final修饰,所以可以多次赋值,改变其指向对象,这点与String有着本质的区别。

//都是其父类的成员属性
char[] value;//内部维护的数组
int count;//数组内被字符占用的下标个数

1.1.2四种构造方法

//初始化内部维护的数组长度16,内容为null
public StringBuilder() {
        super(16);
    }

//指定长度
public StringBuilder(int capacity) {
        super(capacity);
    }

//借助String对象实例化该对象,长度比其长16
public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);//后面分析
    }

//借助CharSequence对象,不为本文重点
public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

1.1.3详细分析的方法

append重载方法有很多,本文只以其中一个为例

//将任意对象转为String后添加
public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

//本文重点攻克方法
public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

//父类的append方法
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);//字符总个数
        str.getChars(0, len, value, count);将str的全部复制到value的开始下标count之后
        count += len;
        return this;
    }

//如果数组已用下标+新加入字符长度比数组总长度要大,
//即数组可用空间小了,就创建新数组
//新数组长度为之前两倍加2,如果还不够:
//新数组长度就为数组已用下标+新加入字符长度
private void ensureCapacityInternal(int minimumCapacity) {
        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;
    }

//从dst的dstBagin开始,将调用对象的srcBagin到srcEnd范围字符复制过去
//即将str的全部复制到value的开始下标之后
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);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

System.arraycopy(a[],b,c[],d,f)方法不仅在append方法中被调用,在delete,insert,replace,以及Arrays类的copyOf等对数组进行操作的方法中也被调用,它的功能是:将从a的b下标开始遍历f长度的元素放到c的d下标之后的位置上。

1.2StringBuffer类,它与builder继承自同一超类AbstractStringBuilder

1.2.1成员属性

除了继承父类的两个成员属性外,还有一个专有属性:缓冲数组,用于储存缓冲数据,在调用toString方法时初始化

private transient char[] toStringCache;

1.2.2构造方法

与builder没有差异

1.2.3重点分析方法(与上述Builder中的方法的同名方法和toString方法)

上述builder中的方法与Buffer的方法的区别:

1.每次对value进行增删改操作时都会将Buffer里面的缓存数组清零

2.Buffer是线程安全的,原因就在于这些对value操作的方法都被锁上了

例如:

//和builder相比多出了一个将缓冲数组清零的语句
//以及多了一个锁
public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

此外Buffer中的toString方法也比较特殊:

它在调用时会将value内的字符串缓存到toStringChche数组上,再次调用时就不需再次遍历数组,直接给实例化的String对象内部维护的数组赋值即可,减少了StringBuffer再次转换为String时的遍历过程。

(如果是使用new String(StringBuffer sb)的方式将StringBuffer转为String,会调用Arrays.copyOf(...)方法,这个方法会调用System.arraycopy(...)方法,遍历一个数组给另一个数组添加元素,再次实例化时依旧再走一遍这个流程,每实例化一次遍历一次)

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

2总结(即回答标题问题)

2.1StringBuilder和StringBuffer是如何做到长度可变:

他们的数组长度并不是正好等于内部字符个数,而是有一个固定值,一旦加入字符数组容纳不下便会对数组进行扩容,此时会有新的数组对象产生,扩容的长度不少于原值的两倍+2(达到数组容量上限除外)

2.2两者的区别

它们的主要区别有两个:

一是两者对线程的安全性不同,和StringBuffer内部对数组进行修改操作的方法都被synchronized关键字锁上了,所以它是同步的、安全的但速度较慢的。

二是StringBuffer具有缓冲机制,它在调用toString方法时将此时的内部数组保存到缓存数组上,在没有调用修改数组的方法时(这些方法会还原缓存数组),下一次再次调用就直接将缓存数组赋值给String内部的数组类型的变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值