深入浅出StringBuilder添加时如何自动扩容

深入浅出StringBuilder添加时如何自动扩容

StringBuilder概述

​ 一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步(线程不安全,效率高)

StringBuilder作用

​ 主要作用于字符串拼接

String和StringBuilder比较

问: String,String也能做字符串拼接,直接用+即可,但是为啥要用StringBuilder去拼接呢?
原因:
①String每拼接一次,就会产生新的字符串对象,就会在堆内存中开辟一个空间,如果拼接次数多了,比较占用内存,效率比较低
②StringBuilder,底层自带一个缓冲区,拼接字符串之后都会在此缓冲区中保存,在拼接的过程中,不会随意产生新对象,比较节省内存

StringBuilder的特点

  • 底层自带缓冲区,此缓冲区为没有被final修饰的char数组(jdk8之后是byte数组),默认长度为16
  • 如果超出了数组长度,数组会自动扩容
  • 创建一个指定长度的新数组,将老数组的元素复制到新数组中,然后将新数组的地址值重新赋值给老数组
  • 每次扩容2倍+2

StringBuilder的使用

构造使用

StringBuilder()
StringBuilder(String str)    

常用方法

StringBuilder append(任意类型数据) -> 字符串拼接,返回的是StringBuilder自己
StringBuilder reverse()-> 字符串翻转,返回的是StringBuilder自己
String toString() ->StringBuilder转成String ->StringBuilder拼字符串是为了快,节省内存空间,那么拼完之后我们后续可能会处理拼接好的字符串,所以我们需要将StringBuilder转成String,才能调用String中的方法去处理拼接好的字符串

StringBuilder自动扩容源码解析

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对象在添加中的自动扩容

你可能感兴趣的:(java,开发语言)