
相信大家对 String、StringBuffer和StringBuilder都非常了解,其中StringBuffer和StringBuilder基本差不多,因此本篇博客将以StringBuffer为入口进行源码分析,并说明String、StringBuffer和StringBuilder的区别以及StringBuffer 底层的数组扩容机制!
之前我对String的不可变性做了总结,相反的java也有可变的字符序列StringBuffer和StringBuilder,我们先在这里总结三者的异同!
String str = new String();// byte[] value = new byte[0];
String st1= new String("aniu");// byte[] value = new byte[]{'a ','n','i','u'};
上面的代码是String的底层以字节数组保存字符串。
接下来我们看看StringBuffer:

如图,可以看到StringBuffer继承了一个AbstractStringBuilder,同样的StringBuilder也继承了这个类,他们的底层都在这个类里!

如上图,现在知道为啥StringBuffer是线程安全的了吧,将所有方法重写并用synchronized关键字修饰,而StringBuilder则没有,因此,StringBuffer更适合在多线程的情况下使用,StringBuilder更适合在单线程情况下使用!
接下来我们看看他继承的AbstractStringBuilder:

可以看到,StringBuffer 依旧是以字节数组保存字符串的,但这个字节数组不像String,并未使用final关键字修饰,因此他是可变的,即修改字符串本质上就是修改这个字节数组。这里的count计数的是实际字符串的长度。
接着看这行代码:
StringBuffer sb1= new StringBuffer();// byte[] value = new byte[16]//底层创建了一个长度是16的字节数组
这段代码创建了一个空的字符串对象,来看看底层:

调用StringBuffer的空参构造,接着super调用父类,也就是AbstractStringBuilder的构造器,且带了个值为16的capacity。
这个capacity是啥我们接着到AbstractStringBuilder中去看:

很明显,这个value就是我们之前申明的byte[]字节数组,这里创建了一个长度为16的字节数组,capacity是数组长度。
总结:StringBuffer sb1= new StringBuffer() 底层创建了一个长度为16的字节数组
同理,我们看看这句代码:
StringBuffer sb2 = new StringBuffer("aniu"); // byte[] value = new byte["aniu".length() + 16]{'a ','n','i','u'};


如图,这里字节数组的长度capacity为你传入的参数str的长度+16,最后创建了一个长度为str的长度+16的字节数组,然后将参数str字符串append到了新创建的字节数组中!
总结:StringBuffer sb2 = new StringBuffer(“aniu”); 底层创建了一个 “aniu”.length()+16 的字节数组(前提是字符串里无中文字符,有中文字符就比较复杂,感兴趣的可以去看看源码,和coder这个编码有关)
随着源码的分析,也产生了两个问题,我们来看看:
通过上面的分析,我们知道,StringBuffer 是底层创建了一个定长的字节数组,来保存字符串,对于多余的空位,值为0。
看下图我的调试:

恰好印证了上面字节数组长度str.length()+16。
因此,对于StringBuffer创建的字符串,str.length()的值可能会认为是底层字节数组的长度,但实际上是字符串的长度,这也符合正常的道理。
对于这个length()我们依旧查看源码:

可以看到他返回的是字符串的长度count,并不是字节数组value.length。
通过前面的分析,我们已经知道,StringBuffer底层创建了一个定长的字节数组来保存字符串。那么当我们不断append添加字符,这个字符串长度超过这个数组长度保存不下了怎么办,这就需要给数组扩容,我们从源码来看看StringBuffer的扩容机制!


如图,我们看到了这个ensureCapacityInternal方法,从字面来看意思就是确保内部容量,他有个参数为count+len,即你append后字符串的总长度。
我们接着来看这个方法内部:

我们可以看到还涉及到二进制运算的左移和右移,和coder这个参数有关,这个参数大家可以自己去看源码里的注释,这里我大概说一说,前面我们源码的截图也看到了,这个coder是编码方式,如果字符串中无中文,为Latin1编码,有中文时为UTF-16编码,我们知道这个UTF-16编码中文则需两个字节,也就是说只要字符串中含有中文,字节数组都是每两位对应一个字符。

如图,我们可以看到coder为UTF-16编码时值为1,因此要考虑<<和>>这些位运算,因此我们减小难度,分析字符串无中文情况吧,此时coder为Latin1编码,值为0,位运算不影响。

可以看到如果新字符串的长度-数组的定长>0,则说明需要给字节数组扩容,
这时用Arrays.copyOf方法将当前数组的值拷贝给了newCapacity()长度的新数组,最后又赋值给了字节数组value。
大概原理其实已经讲清楚了,但是我们还是要深入一点,看看newCapacity()这个方法的返回值,这恰好是数组扩容的核心:

这些参数我不再解释,分析到这我也累了!
最终返回的长度是length,因此我们换要继续深入:

最终数组长度由 Math.max(minGrowth, prefGrowth) 决定!
到此我们分析完了,大概总结一下:
当增量minGrowth>prefGrowth(字节数组定长+2),扩容后的字节数组长度为oldLength + minGrowth,即增量+字节数组定长。
当增量比较小,小于prefGrowth(字节数组定长+2) 时,扩容后的数组长度为字节数组定长*2 +2 。
StringBuilder 和 StringBuffer的底层一样,大家可以自行去看,另外coder这个参数我也说了,感兴趣的可以看看字符串有中文时StringBuilder底层数组的定长和扩容算法。
如果你觉得博主写的还不错的话,可以关注一下当前专栏,博主会更完这个系列的哦!也欢迎订阅博主的其他好的专栏。
🏰系列专栏
👉软磨 css
👉硬泡 javascript
👉flask框架快速入门