StringBuffer源码分析
String和StringBuffer肯定都是通过char[]进行存储的但是他们真的底层创建方式完全一样?
我们先来看一下String的底层是如何创建字符串对象的
String str = new String();
这里在底层是创建了一个长度为零的字符数组
- 相当于new char[0];
String str1=new String("abc");
这里在底层是创建了一个长度为三的数组
- 相当于new char[]{‘a’,‘b’,‘c’};
通过上面的分析我们可以发现,String类创建出的对象中的char[]的长度就等于创建的字符串的长度
这里我们再来看一下StringBuffer底层是如何创建char[]的
StringBuffer sb1=new StringBuffer();
这里在底层是创建了一个长度为16的char[]
- 相当于new char[16];
StringBuffer sb2=new StringBuffer("abc");
这里在底层是创建了一个长度为19的char[] (其实也就是创建的数组长度等于创建的字符串长度加16)
- 相当于new char[“abc”.length+16];
通过上面的问题StringBuffer创建对象的底层分析我们可以得到,我们底层创建的char[]的长度总是比我们要创建的字符串的长度长16,也就是字符串长度+16就是底层char[]的长度
为什么StringBuffer和StringBuilder是可变字符序列?(StringBuffer和StringBuilder是一样的,所以我们这里以StringBuffer为例类比StringBuilder)
我们知道String中的value数组被final关键字所修饰,所以String是不可变字符
而StringBuffer底层的char[]数组没有被final关键字修饰,那么我们就可以说StringBuffer是不可变字符序列?
- 就算我们StringBuffer底层的char[]没有被final关键字修饰,也只是可以改变数组中存储的值,但我们知道数组的长度是给定的,数组长度一旦给定之后就不能再改变了,那么如果我们要对StringBuffer进行增长,就不能满足了,所以我们的StringBuffer对字符串在底层的char[]创建时就和String不一样,StringBuffer对字符串的存储的char[]的长度要比你创建的字符串的长度长16,所以说如果长度为3的字符串创建时底层的char[]的长度为19,这样才可以达到字符串可变的目的
也就是字符串可变不仅仅是要考虑字符串值的改变,还要考虑到字符串的长度的变化,所以说StringBuffer字符串不仅仅是要考虑底层的char[]没有被final关键字修饰,还要考虑这个字符串在创建时的初始长度
那么相应的也会出现一些问题:
问题1:
StringBuffer sb2=new String("abc");
- 这个时候输出(sb2.length())的结果是多少?—是3,因为我们这个StringBuffer可变序列中是判断char[]中由内容不为默认的长度(也就是看char[]中有实际存储的内容的长度),也就是这个创建的长度为19的char[]只有前面三个内容是不为默认的
问题2:
扩容问题:
如果要添加的数据底层数组装不下了,那就需要对底层数组扩进行容
- 默认情况下,我们会将底层数组扩容为原来的数组长度的二倍加2,同时将原来的数组的元素复制到新的数组中
- 除了默认情况之外我们还有其他情况,比如说如果我们的数组扩容成原来数组长度的2倍+2之后还是不够,那么这个时候我们我们就会将这个数组扩容到刚刚好的容量大小(也就是刚刚可以将所有的元素全部装入)
- 在StringBuffer中的扩容问题中涉及到了数组的复制
这个扩容问题的指导意义:
- 因为扩容我们需要可能需要多次判断,还需要对数组进行赋值,所以会一定程度上影响我们程序的执行效率,所以我们要尽量避免数组扩容的发生
所以在我们的实际开发中我们建议使用StringBuffer(int capacity)方法来构造StringBuffer对象,就是指定我们创建底层的char[]的长度,这样我们自己就相当于添加了认为的判断,也就是我们自己写程序我们肯定大概知道这个字符串大概需要扩容到多大为止,这样就可以避免我们一个数组扩容后将会很长,这个时候我们如果还是默认创建方式的话创建出来的字符串就可能是需要多次扩容的,会大大影响我们的编程效率