StringBuilder扩容原理及源码分析

使用无参构造方法创建对象

首先我们通过StringBuilder的无参构造方法创建一个StringBuilder对象sb,

可以看到源码中,当我们使用无参构造创建对象时,默认为我们提供了一个容量(capacity)大小为16的给其父类。

在其父类中,收到子类StringBuilder传来的capacity值为16,,此处我们可以看到这里有一个参数COMPACT_STRINGS,为了优化字符串在JVM中的内存占用,Java 9引入了Compact Strings来取代Java 6的Compressed Strings,使用byte[]来替代char[],并且引入了一个字段coder来标识是LATIN1还是UTF-16。

进入到COMPACT_STRINGS,咱们可以看到一个静态代码块,为COMPACT_STRINGS赋值为true。那么便要进入if语句中。

在if语句中,我们可以看到我们创建了名叫value的一个byte数组,coder为LATIN1,之前我有提到引入了一个字段coder来标识是LATIN1还是UTF-16,那么此处coder就标识为LATIN1。

这就是无参构造方法创建对象时,底层的源码,可以看到现在sb对象的容量为16,长度为0,那是因为咱们还没有存储对象。

使用append方法添加字符串

咱们先使用append方法去添加一个大小为16个字符的对象,看看底层会怎么走。

可以看到图上咱们使用append方法去添加一个大小为16个字符的对象时,他将这个对象丢给了他的父类AbstractStringBuilder,

进入父类,咱们可以看到添加的不为空,那么走到len这边,在length()方法中咱们看见了老熟人coder,之前咱们有说到coder就标识为LATIN1,这里就用上了。

当coder标识为LATIN1时,表示字符串只包含 LATIN-1编码的字符,coder 变量的值将为0,咱们现在看到的时一个三元表达式,如果是true就返回第一个值,false返回第二个值,而COMPACT_STRINGS之前是为true的,所以返回了coder=0的值。

从上面我们可以看到他的长度并没有发生改变还是16。

接下来又看到了咱们的老朋友coder,他还是0,所以oldCapacity会等于str的长度16,minimumCapacity的值为count加length,count的初始值为0,所以minimumCapacity也是16。

这里有一个count是扩容机制中关键的元素,在每次添加字符串长度后,他都会记录长度,此处count=16。

查看结果,和分析的一致,

那么在超出容量会怎么样了?

超出容量是扩容机制的底层原理

再添加一个字符串,来看看再超出容量时,StringBuilder的扩容机制怎么实现的

首先,依然是调用StringBuilder的append方法将这个字符串给了父类,

父类先判断传过来的字符串是不是空的,不是空的,就到len这一步,

可以看到coder还是0,len为1;

而ensureCapacityInternal中的参数count在之前添加过元素后变为16了,所以这边的minimumCapacity其实是等于17的;

 

然而value.length依然还是16,minimumCapacity-oldCapacity

现在已经是大于0了,

 

扩容机制的核心代码来了:

value = Arrays.copyOf(value,newCapacity(minimumCapacity) << coder);

这句代码的意思是复制value数组的内容,长度为newCapacity(minimumCapacity) << coder。

 

之前minimumCapacity是等于17了的。oldCapacity依然为16,但是咱们注意到int newCapacity = (oldCapacity << 1) + 2;

(<<的意思是二进制向左移动一位,移动一位相当于乘以2,移动几位就是乘以2的几次幂。>>的意思就是向右移动,移动几位就相当于除以2的几次幂。)

这句话可以使用一个式子代替为:新的容量=旧的容量*2+2,所以我们不难得出新的容量为34。

当添加字符串超过 当前容量*(2^1)时:

此处我添加35个字符,得到的结果好像和之前没啥关系,接着往下看。

 

可以看见minCapacity变为35,oldCapacity为16,newCapacity=16*(2^1)+2=34。

newCapacity-minCapacity<0进入if;

把minCapacity的值复制给了newCapacity。这就是容量等于35,长度也是35的原因。

 

使用有参构造方法创建对象

使用有参构造方法创建StringBuilder对象,

这里我也创建一个大小为14的字符串对象,看看是否和无参是一样的。

可以看见和无参明显不一样,无参中的super方法中值为16,而有参方法确实字符串的长度加16,super(str.length() + 16);

进入length()方法算字符串的长度为14,14+16=30,所以他的容量为30。

接着往下走,COMPACT_STRINGS为true(前面有说到),进去if语句中,创建了一个大小为capacity(30)的byte数组,coder还是标识的LATIN1。

 

然后使用append方法把字符串添加进之前创建的byte数组(与之前使用append同理)。

 

综上我们可以知道:

1、无参调用时,我们的字符串默认初始容量为16.

2、 a、当添加字符串不超过容量时,容量不发生变化,长度为字符串长度。

      b、当添加字符串超过容量时,容量发生变化,计算公式为:

     添加后容量=当前容量*(2^1)

  适用于添加字符串长度+加上本身长度<当前容量*(2^1)的情况;

     c、当添加的字符串超过当前容量*(2^1)时:

               添加后容量=字符串长度+当前容量

3、StringBuilder存储对象,说白了就是底层了byte数组在进行存储。

       我们在实际应用中应当避免StringBuilder频繁的扩容,节约资源,如果将来有需要使用StringBuilder方法添加字符串,且不知道添加多少时,可以使用StringBuilder​(int capacity)方法构建一个没有字符的初始容量为capacity的字符串构造器,避免资源浪费。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值