一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步(线程不安全,效率高)
主要作用于字符串拼接
问: String,String也能做字符串拼接,直接用+即可,但是为啥要用StringBuilder去拼接呢?
原因:
①String每拼接一次,就会产生新的字符串对象,就会在堆内存中开辟一个空间,如果拼接次数多了,比较占用内存,效率比较低
②StringBuilder,底层自带一个缓冲区,拼接字符串之后都会在此缓冲区中保存,在拼接的过程中,不会随意产生新对象,比较节省内存
构造使用
StringBuilder()
StringBuilder(String str)
常用方法
StringBuilder append(任意类型数据) -> 字符串拼接,返回的是StringBuilder自己
StringBuilder reverse()-> 字符串翻转,返回的是StringBuilder自己
String toString() -> 将StringBuilder转成String -> 用StringBuilder拼字符串是为了快,节省内存空间,那么拼完之后我们后续可能会处理拼接好的字符串,所以我们需要将StringBuilder转成String,才能调用String中的方法去处理拼接好的字符串
public class Exercise {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("b");
}
}
这里我们首先创建一个StringBuilder对象sb , 采用无参构造器创建 , 查看StringBuilder和String的源码可以发现 , String底层是一个被final修饰的char[]数组 , 而StringBuilder底层是一个没有被final修饰的char[]数组
private final char value[]; //String类
abstract class AbstractStringBuilder implements Appendable, CharSequence{
char[] value; //StringBuilder类
int count;
}
当我们调用append()方法进行元素添加时 , 此时我们进入append() 的底层源码
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
进入以后携带形参String str , 调用父类的append(str)方法传递 , 此时我们进入父类super
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;
}
首先我们执行append方法中的步骤
if (str == null)
return appendNull();
可以看到首先将传入的形参str与null做比较, 判断是否传入的是空对象 , 如果不是 , 则不进入if语句进行判断 . 否则 , 进入if语句进行判断 , 可以看到 如果传入的str为空 , 则返回 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;
}
我们进入appendNull()方法可以发现 , 首先它先将count赋值给了一个int型的变量c , 然后调用ensureCapacityInternal(minimumCapacoty)方法 , 此处为ensureCapacityInternal(c + 4);旨在判断是否需要扩容 , 因为传入的是一个空 , 即为null , 在char[]中占4个字节 . 此时我们进入ensureCapacityInternal(minimumCapacoty)方法
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
可以看到 , 首先是将传入的形参minimumCapacity 与 底层char[] 的数组长度作比较 (底层的char[]数组长度默认为16) . 如果minimumCapacity - 16 > 0 , 也就意味着原数组中已有的元素加上新添加的元素和超过了16 , 那么就需要对数组扩容 , 此时底层调用了Arrays.copyOf()方法
public static char[] copyOf(char[] original, int newLength)
参数:
original - 要复制的数组
newLength - 要返回的副本的长度
此处我们要进行数组扩容 , 我们将原来的数组value 置于第一个参数 , 需要对他进行复制操作 , 那么第二个参数则需要设置返回新数组的扩容长度 , 此处底层调用了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;
}
进入newCapacity(int minCapacity)方法 , 首先形参接收了minCapacity参数 , 也就是count + str.length()的长度 , 底层默认扩大原value.length的2倍+2 ; 此时以备不时之需 , 将扩大后的newCapacity与传入的minCapacity作比较,如果仍然小于minCapacity , 那么直接将minCapacity 赋值成新数组扩容后的长度 , 即newCapacity . 赋值完成后将newCapacity返回 . 返回时底层采用了三元表达式
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
如果 , newCapacity < = 0 或者 MAX_ARRAY_SIZE - newCapacity < 0) 为false时 , 才会返回newCapacity
返回结束后 , 我们的新扩容后的数组 也就意味着创建成功
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
将扩容后的数组重写指向value , 然后将value用final修饰 , 将null 分别传入value的索引值 , 然后重新赋值count返回
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
如果此时传入的str不为空 , 我们需要将str中的内容添加到扩容后的数组中 , 底层调用了getChars方法
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
字符从此序列复制到目标字符数组 dst 中。要复制的第一个字符位于索引 srcBegin ;要复制的最后一个字符位于索引 srcEnd-1 处。要复制的字符总数为 srcEnd-srcBegin 。字符被复制到 dst 的子数组中,从索引 dstBegin 开始到索引结束
dstbegin + (srcEnd-srcBegin) - 1
参数:
srcBegin - 以此偏移量开始复制。
srcEnd - 在此偏移处停止复制。
dst - 将数据复制到的数组。
dstBegin - 偏移到 dst。
str.getChars(0, len, value, count);
count = c;
return this;
也就是说要将str底层的char[] 数组的全部内容赋值至扩容后的value[count]之后 , 然后重新赋值count , 返回value
至此 , 我们完成了StringBuilder对象在添加中的自动扩容