1. 介绍

String中还有两个常用的类,StringBuffer和StringBuilder。这两个类都是专门为频繁进行拼接字符串而准备的。最先出现的是StringBuffer,之后到jdk1.5的时候才有了StringBuilder。
2. StringBuilder解析

从这张继承结构图可以看出:
- StringBuilder继承了AbstractStringBuilder
- StringBuilder实现了Comparable接口,这说明他是可比较的
- StringBuilder实现了CharSequence接口,这说明他是一个可变的字符序列
- StringBuilder实现了Appendable接口,这说明他具有可追加字符序列的能力
- StringBuilder实现了Serializable接口,这说明他可以被序列化
StringBuilder继承了AbstractStringBuilder,而AbstractStringBuilder的底层是一个byte数组,并且前面没有加final进行修饰,因此他是可变的,可以进行扩容,而String类就是因为这个byte数组加了final关键字进行修饰,因此String是不可变的。那么既然StringBuilder可以进行扩容,在具体进行扩容操作又是如何实现的呢?接下来就要继续从源码进行查看。
StringBuilder有一个无参构造方法,默认初始化的容量是16.
2.1 第一个例子
接下来我们写一个具体的程序来看一下,在扩容的时候是如何进行操作的。
java
public class test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("Hello World");
sb.append("123456");
sb.append("3.1415926");
System.out.println(sb);
}
}
然后开始debug进行查看。首先是进入到Stringbuilder的这个append()方法
然后下一步会进入父类的append()方法。
这个方法就是确保他的容量,这里有一个count参数,代表最小容量,这个参数是指当前这个对象里面真实存储的元素的个数,现在是0,意味着这个对象创建出来之后,里面暂时还没有传入任何值。
进入到这个方法之后,这里有一个oldCapacity变量,因为初始化容量是16,因此这里这个值也是16,然后下一步判断这个最小容量minimumCapacity减去这个老容量oldCapacity是否大于0,此时可以看到值大于0为false,因此不会进去if中的扩容操作。
然后再看下一次的append()操作
同样的走到这一步,这个时候可以看见,最小容量minimumCapacity减去这个老容量oldCapacity大于0了,说明这里会进行扩容的操作,就会进入到newCapacity()这个方法。
到这一步可以看到此时的oldLength就是初始化的长度为16,newLength是预期的长度,也就是这个字符串拼接进去之后的长度,为17,增长就为17 - 16 = 1,接下来就要进入到ArraysSupport.newLength()这个方法中查看新长度为多少。
进入到方法之后,然后这里使用oldLength,也就是16,去加上Math.max(minGrowth, prefGrowth),在最小增长和预期增长之间的最大值,这里minGrowth为1,prefGrowth为18,因此最大值就是18,然后就是16 + 18 = 34。
然后回到newCapacity()方法,也可以看到length为34.
继续往下走,发现此时value值为34
当我们不指定初始容量时,会给一个默认的初始容量为16,从上面可以看到进行第一次扩容之后,新的容量为34.
2.2 第二个例子
我们在写一个程序进行测试,这次拼接的字符串稍微大一点
java
public class test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("3.1415926535897932384626433832795028841971");
System.out.println(sb);
}
}
还是一样先进入append()方法
再进入父类的append()方法,可以看到现在长度为42
然后进入到ensureCapacityInternal()方法,这次可以看到最小容量为42,旧容量为16,42 - 16 > 0,因此要进行扩容。
接着进入到newCapacity()方法,这个时候的增长growth就是42 - 16 = 26了,然后下一步进入ArraysSupport.newLength()方法
进去之后oldLength不变,还是16,但是这次的Math.max(minGrowth, prefGrowth),minGrowth为26,prefGrowth为18,那么最大值就是26,所以prefLength的值为42。后续步骤就和之前一样了。
当我们不指定长度,并且添加的字符串长度过长时,扩容的时候,就直接使用当前这个字符串的长度。
如果这个时候,再添加一个字符串,比如sb.append("helloworld123");这个时候又会是什么样的呢?可以看到这个时候oldLength就是我们第一次拼接的长度为42,新长度为55,增长长度为 55 - 42 = 13,继续往下走
这个时候可以看到oldLength为42,Math.max(minGrowth, prefGrowth)方法取的最大值,最大值现在为44,因此扩容后的长度就是42 + 44 = 86.
那么扩容的测试到这里就结束了。我们可以看到,第一次初始化容量为16时,扩容容量为34,为16 * 2 + 2,这次再进行二次拼接时一开始为42,扩容容量为86,为42 * 2 + 2 ,可以明显看出每次扩容是原来容量的2倍再加2.原因就是因为这段代码,从上面调试来看,扩容时都是oldLength + (oldLength + 2),即oldLength * 2 + 2.
3. 扩容总结
- StringBuilder的扩容策略是从当前容量开始,每次扩容为原来的2倍再加2
- 如果一开始拼接的字符串的长度就超过了默认初始容量16时,那么StringBuilder扩容的容量就直接为一开始的字符串长度
- 如果我们想要进行优化,那么就要在创建StringBuilder时,预估一下我们要拼接的字符串的长度,然后给定一个合适的初始化容量,从而减少底层的扩容操作。
4. 构造方法和常用方法
4.1 构造方法
方法 | 描述 |
---|---|
StringBuilder() | 构造一个字符串生成器,其中不包含任何字符,初始容量为16个字符 |
StringBuilder(int capacity) | 构造一个字符串生成器,其中不包含任何字符,并且具有由容量参数指定的初始容量 |
StringBuilder(String str) | 构造初始化为指定字符串内容的字符串生成器 |
用法如下:
4.2 常用方法
-
append(),追加方法,这个里面可以传多种不同类型的值
-
delete():删除方法
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
/**
* delete(int start, int end)
* 删除从指定位置开始,到指定位置结束的字符
* 左闭右开:[start, end]
*/
sb1.delete(0,3);
System.out.println(sb1); //defg
/**
* deleteCharAt(int index)
* 删除指定位置的字符
*/
StringBuilder sb2 = new StringBuilder("abcdefg");
sb2.deleteCharAt(5); // abcdeg
System.out.println(sb2);
}
}
- insert():插入方法,在指定位置添加的方法,可以添加多种类型的值
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
/**
* insert(int offset, String str)
* 在指定位置上添加一个字符串
*/
sb1.insert(3, "HELLO"); // abcHELLOdefg
System.out.println(sb1);
}
}
- replace(int start, int end, String str):替换方法
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
/**
* replace(int start, int end, String str)
* 从指定位置开始到结束,替换成新的字符串
* 同样也是左闭右开
*/
sb1.replace(1, 3, "ABC"); // aABCdefg
System.out.println(sb1);
}
}
- reverse():反转方法
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
sb1.reverse();
System.out.println(sb1); // gfedcba
}
}
- setCharAt(int index, char ch):设置某个位置为指定的字符
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
sb1.setCharAt(0, 'A');
System.out.println(sb1); // Abcdefg
}
}
- setLength(int newLength):设置新的长度
java
public class test {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abcdefg");
sb1.setLength(3);
System.out.println(sb1); // abc
}
}
剩下的一些方法,在用到的时候看一下即可,都不是很难。