讲一下string和stringbuilder,stringbuffer的区别以及capacity的扩增机制——源码分析

讲一下string和stringbuilder,stringbuffer的区别以及capacity的扩增机制——源码分析

操作字符串:string\stringbuilder\stringbuffer

string是内部不可变的字符串,string底层使用了一个不可变的字符数组(final char[])

stringbuilder和stringbuffer是内容可变的字符串,底层没有使用final

stringbuilder和stringbuffer的区别在于stringbuilder是线程不安全的,stringbuffer是线程安全的。

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5Nefmxz-1598360757642)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\1598355748222.png)]

可以看到stringbuilder和stringbuffer通过append(xxx)方法来动态追加字符串,而string则不能。下面通过分析源码来加深自己的认识。(之前都是死记硬背,以后能看源码的自己看源码来分析。)

package csdn.hsy.cool.blog.ms;

public class StrTest {
    public static void main(String[] args) {
        String strH="haosy";
        StringBuilder strS=new StringBuilder("stringbuilderHaosy");
        StringBuffer strY=new StringBuffer("stringbufferHaosy");
        strY.append("xxx");
        strS.append("xxxxx");
        System.out.println(strY);
    }
}
 String strH="haosy";

分析一下这一句,我们都知道,"haosy"其实是 new String(“haosy”);,这个就不多少了吧,那么我们来看一下构造器到底做了什么。

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

可以看到把值赋给了什么?this.value是什么?跟踪一下

    private final char value[];

呼,看到了没?这是一个final,所以你懂了没?string不可变应该明白了吧。

那我们来看一下stringbuffer和stringbuiler到底又是为什么可变的。

先从我们知道的入手,最起码我相信你应该比我聪明把,append()方法最起码你会用把,我们就从它入手看一下,找找看能不能看出什么端倪。

        strY.append("xxx");

看下这句,跟踪一下append的实现原理

   @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

先忽略其它的代码,看 super.append(str);这句,我们跟踪一下父类的方法

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

同样,先忽略其它代码,遵循先深度后广度的原则跟踪分析一下代码

看这句str.getChars(0, len, value, count);

其实这句代码的意思你应该明白,getChars()方法

public void getChars(int srcBegin,
                     int srcEnd,
                     char[] dst,
                     int dstBegin)
将此字符串中的字符复制到目标字符数组中
参数 
srcBegin - 要复制的字符串中第一个字符的索引。 
srcEnd - 要复制的字符串中最后一个字符后面的索引。 
dst - 目标数组。 
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);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

我想让你知道的是,第3个参数是一个字符数组,看这句str.getChars(0, len, value, count);中的value要知道是一个数组,跟踪一下。

char[] value;

看到了把,没有final,所以可变,明白了吧。可能你还不明白,迷迷糊糊,下面深入分析一下append方法,刚才我们忽略的代码下面我们来刨析。

特别注意:源码主要分析了StringBuffer,Stringbuilder没有分析,因为比较相似,如果有兴趣,请自行分析,也相信你会很有用兴趣。

======================================================================================

append()方法深入分析

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

首先可以看到synchronized,线程安全的,这个stringbuilder是有所不同的,来看一下

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

所以,你要问我StringBuffer和StringBuilder有什么区别?我回答StringBuffer是线程安全的,但是效率会低,StringBuilder是非线程安全的,所以效率高。好,一笔带过,让我们下文不要再分析StringBuilder了好么?留给你自己吧~

回来老弟

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
 toStringCache = null;

这是做什么的?定位了整个StringBuffer

    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
======================================================================
     /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

看到这句注释返回最后一次toString的缓存值,一旦StringBuffer被修改就清除这个缓存值。

想了半天想不明白到底是做什么的.但是知道这是一个缓存相关的,然后我去StringBuilder中看了一眼竟然没有这个,霍,我懂了,你呢?

StringBuffer线程安全,到处synchronized关键字,性能与StringBuilder相比大打折扣,线程安全应用场景多线程,访问的数量级肯定比单线程环境要大的多,所以弄个缓存,可以平衡StringBuffer的性能。

回来老弟

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
super.append(str);

调用父类的append方法啊,最后再返回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);
        count += len;
        return this;
    }
  if (str == null)
  return appendNull();

一看就知道了吧,追加了个空气,奉上源码appendNull(),里面的ensureCapacityInternal我们先放它一马,跑不掉的。

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

老弟回来

    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;
    }
        int len = str.length();
        ensureCapacityInternal(count + len);

看下这,得到追加字符串的长度len,然后调用ensureCapacityInternal方法,确保内部容量?晕乎,看不懂,跟踪一下

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

得,引出了stringbuffer得扩充机制了。对不起,我的错,来分析一波吧

看到一个新的方法newCapacity(),上源码

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

int newCapacity = (value.length << 1) + 2; << 左移,不好理解,说简单点

int newCapacity = (value.length)*2 + 2;

扩充?如何扩充?放大两倍再加2

 if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }

如果如果minimumCapacity大于原容量*2+2,那么新容量就取值为minimumCapacity,反之扩充就采用2倍+2原则,相信这个结论你之前都听过,现在应该明白了吧

上代码看看是否是这样的?

   System.out.println("扩充机制");
        StringBuffer sb1 = new StringBuffer();
        for (int num = 0; num < 5; num++) {
            sb1.append("12345678");
            System.out.println(" this capacity: " + sb1.capacity());
            System.out.println(" this length: " + sb1.length());
            System.out.println("---------------------------------");
        }

结果

扩充机制
 this capacity: 16
 this length: 8
---------------------------------
 this capacity: 16
 this length: 16
---------------------------------
 this capacity: 34
 this length: 24
---------------------------------
 this capacity: 34
 this length: 32
---------------------------------
 this capacity: 70
 this length: 40
---------------------------------

说明我们分析的没毛病

扩充机制到此结束了么?并没有

看上面的结果并不是每次都扩充,只有大于原本的容量才扩充,这又是为什么呢?附源码

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(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);
}

综上:扩充机制?

1、小于当前容量时,容量不变。
本例中,容量依然为16。
2、大于当前容量,并且小于(当前容量+1)*2,则容量变为(当前容量+1)*2。
本例中,16<20<(16+1)*2=34,所以容量为34。
3、大于当前容量,并且大于(当前容量+1)*2,则容量变为用户所设置的容量。
本例中,80>16,80>(16+1)*2=34,所以容量为80。

好,兄弟,回来,扩充机制只是迫不得已分析的,我们还是要分析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;
    }
  str.getChars(0, len, value, count);

把结果放入value中

 count += len;

记录当前的长度,用于后续扩充的判断条件

return this;

返回,目前在AbstractStringBuilder这个父类中,回溯到调用出StringBuffer的append方法

  @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

回溯,返回最终的结果。

源码分析:一刷 2020/08/25  水平有限,分析难免会有误差,有问题请及时指出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值