1. 概述
StringBuffer
与StringBuilder
都是可变字符序列(字符串)。- 不同的是
StringBuilder
非线程安全,StringBuffer
线程安全。- 查看源码可以发现,
StringBuffer
除构造外的其他方法都直接或间接(重载)用synchronized
修饰。- 这里主要讨论可变长,也就是扩容原理,以
StringBuilder
为例,StringBuffer
与之一致。
2. StringBuilder 的构造方法
// 类定义 public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { // StringBuilder 的构造 public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); } ... ... }
- 4 种构造方法都使用了父类
AbstractStringBuilder
的构造器。- 除了第二种构造外,其他构造种都带有醒目的 16 ,换算后正好是 2 4 2^4 24,感觉没啥联系,emm。
- 无参构造,默认传参 16。
- 参数为 int 类型的构造,传参就为指定的 int 值。
- 参数为 String 类型的构造,传参为 String 的长度 + 16。
- 参数为 CharSequence 类型的构造,调用的是参数为 int 的构造,最终传参为 CharSequence 的长度 + 16。
3. AbstractStringBuilder 构造方法
abstract class AbstractStringBuilder implements Appendable, CharSequence { /* The value is used for character storage. */ /* value 用于存放字符 */ char[] value; /* The count is the number of characters used. */ /* count 是有效字符个数 */ int count; AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; } ... ... }
AbstractStringBuilder
有两个构造方法重载,一个无参构造,一个带 int 类型参数的构造。- 前面
StringBuilder
中都使用的是带参构造。- 看到
new char[capacity]
,心里一下就踏实了,与String
一样,底层都是char
数组,长度就是前面传递过来的长度,不同的是这里没有使用final
关键字修饰。
对于
StringBuilder
的初始化结论如下:
- 对于无参构造,初始大小为:
16
- 对于传入 int 类型的构造,初始大小为:
指定的 int 值
- 对于传入
String
类型和CharSequence
类型的构造,初始大小为:字符序列长度 + 16
4. 扩容机制
- 添加新字符时可能会导致原数组溢出,这时需要对原数组进行扩容,确保新的数组能够容纳新添加的字符序列。
- 扩容是发生在添加操作前的动作,所以不得不提
append()
方法,如下图:
- 有许多重载方法,针对不同类型做不同处理,都大同小异,这里使用添加 String 类型作为示例,定义如下:
@Override public StringBuilder append(String str) { super.append(str); 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; }
- 先处理了 String 为 null 的情况,调用的是 appendNull() 方法,
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; }
- 可以发现,不管是
append()
还是appendNull()
方法,里面都有一个方法ensureCapacityInternal()
,且传递的参数都是count + 字符长度
,这个就是用于扩容的方法,它内部的实现如下:private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } }
- 参数
minimumCapacity
就是count + 待添加字符序列的长度
,如果这个值比原数组的长度小,说明还未存满,不做任何处理。- 反之则需要扩容了,可以看到扩容操作是基于
Arrays.copyOf()
实现的,新数组的长度由newCapacity()
获取,在进入这个方法之前,需要先对参数minimumCapacity
做进一步的解释。- 前面提到
minimumCapacity
是count + 待添加字符序列的长度
,关于 count
,它在AbstractStringBuilder
中的有声明,表示的是字符数组value
中有效字符的个数,即已存储字符的个数。- 令人费解的是,基本类型必须初始化才能使用,但我未能找到
count
初始化相关的代码,只好将它作为默认值 0 处理。【已解决】- 【基本类型作为成员变量时,Java 会自动为其设置默认值】
- 知道
minimumCapacity
的参数的含义后,继续看newCapacity(minimumCapacity)
方法,它的定义如下: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;
value.length<<1
等价于value.length *= 2
,所以扩容后数组的大小为原数组的大小(value.length) * 2 + 2
。
- 众所周知, int 类型指的是 10 进制的整数,但在计算机中,它是以二进制存储的。
- 还记得初学 Java 时,老师教的 int 类型占 4 个字节大小吗?
4 byte == 32 bit
,所以 int 的取值范围 [ − 2 31 , 2 31 − 1 ] [-2^{31} ,{2^{31} - 1}] [−231,231−1]- 说了一堆,就是为了铺垫出 int 表面上是 10 进制,背地里却是二进制,可以将其当作二进制处理。
- 所以
value.length<<1
是将整体像高位移动了一位,就如同十进制的 10 变成了 100,扩大的 10 倍,同理二进制的 0010 变成 0100,扩大了 2 倍,由此得出每次扩容后的大小为原数组的长度 * 2 + 2
5. 总结
1、 StringBuilder 或 StringBuffer 的初始化分配的空间大小取决于调用的构造方法:
- 无参构造默认大小为 16
- 调用 int 类型参数构造,初始化大小为指定的 int 值。(更推荐这种方式,可以减少扩容次数,提高效率。)
- 调用 String 类型或 CharSequence 类型参数的构造,初始化大小为:
字符序列的长度 + 16
2、扩容机制每次扩容大小为:
原数组大小 * 2 + 2
3、补充:
- StringBuilder 调用 length() 方法时,返回值为有效字符个数,它的源码如下:
@Override public int length() { return count; }