java源码-StringBuilder

108 阅读3分钟

啦啦啦,今天又是看源码的一天😀。如果下面我的理解有什么问题,欢迎大佬捶我🙄。

今天看一下StringBuilder,大伙都知道,string是不可变的。当我们需要对字符串做一些追加、插入等操作的时候。我们就要使用到StringBuilderStringBuilder可以看成一个容器。当然,它并不是线程安全的。如果涉及到并发问题,使用StringBuffer

整体上看一下

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

我们发现,stringBuilder继承了AbstractStringBuilder抽象类。点进去看一下

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

这里可以了解到,stringBuilder中用来存储字符串信息的就是这个char[] valuecount是字符串使用了几个char

image.png 好家伙,stringBuffer也是继承了这个抽象类,所以他们在存储字符串的结构上都是一样的。

常用方法的源码

构造方法

/**
 * Creates an AbstractStringBuilder of the specified capacity.
 */
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
public StringBuilder(int capacity) {
    super(capacity);
}
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);

平时我们在new StringBuilder("abc")的时候,就是创建了一个比传入字符串大16的容量的StringBuilder。然后进行append("abc")

append(String s)

// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
    if (sb == null)
        return appendNull();
    int len = sb.length();
    //容量不足就扩容
    ensureCapacityInternal(count + len);
    //把sb的值追加到value的count索引以后
    sb.getChars(0, len, value, count);
    //更新长度
    count += len;
    return this;
}
public StringBuilder append(StringBuffer sb) {
    super.append(sb);
    return this;
}

ensureCapacityInternal(int minimumCapactity)如果容量不足,用来扩容。value.length小于minimunCapacity,就会把容量扩容到2倍+2。

private void ensureCapacityInternal(int minimumCapacity) {
    // 如果容量不足,计算一个新容量,并拷贝到这个新长度的数组上
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}
private int newCapacity(int minCapacity) {
    // 新容量是两倍+2
    int newCapacity = (value.length << 1) + 2;
    // 依旧不足,新容量设置为minCapacity
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    // 溢出或者超过最大容量,重新计算。
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}
private int hugeCapacity(int minCapacity) {
    // 如果是溢出,直接抛异常
    if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
        throw new OutOfMemoryError();
    }
    // 如果比Max_array_size还要大,则设置为minCapacity不变,否则设置为max_array_size。
    return (minCapacity > MAX_ARRAY_SIZE)
        ? minCapacity : MAX_ARRAY_SIZE;
}

这里就很奇怪了,minCapacity > MAX_ARRAY_SIZE(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)的话,就会赋值为minCapacity。为什么MAX_ARRAY_SIZEInteger.MAX_VALUE - 8 呢,为什么有时候又可以超过该值。源码有解释

为什么MAX_ARRAY_SIZE的大小为Integer.MAX_VALUE-8

/**
 * The maximum size of array to allocate (unless necessary).
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
 /**
  * 要分配的数组的最大大小(除非必要)。
  * 有些虚拟机在数组中保留一些头字。
  * 尝试分配更大的阵列可能会导致OutofMemoryError:请求的阵列大小超过虚拟机限制
  */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

因为有些jvm会在数组后面添加一些头数据。减去8的目的是为了减少出错。

我试着做了个实验

public class Test {

    public static void main(String[] args) {
        byte[] bytes = new byte[Integer.MAX_VALUE - 3];
        System.out.println("Integer.MAX_VALUE - 3分配成功");
    }
 }

image.png

public class Test {

    public static void main(String[] args) {
        byte[] bytes = new byte[Integer.MAX_VALUE - 1];
        System.out.println("Integer.MAX_VALUE - 1分配成功");
    }

image.png 我发现在我的实验中,Integer.MAX_VALUE - 1开始就无法分配,数组可分配的最大大小并不是Integer.MAX_VALUE。减去8还是很有必要的,除非有必要,否则把容量限制在Integer.MAX_VALUE - 8内,减少出错。

insert

public AbstractStringBuilder insert(int index, char[] str, int offset,
                                    int len)
{
    if ((index < 0) || (index > length()))
        throw new StringIndexOutOfBoundsException(index);
    if ((offset < 0) || (len < 0) || (offset > str.length - len))
        throw new StringIndexOutOfBoundsException(
            "offset " + offset + ", len " + len + ", str.length "
            + str.length);
    ensureCapacityInternal(count + len);
    // value中index索引位置以后的字符全部往后移动
    System.arraycopy(value, index, value, index + len, count - index);
    // 把要插入的字符串str复制到index位置
    System.arraycopy(str, offset, value, index, len);
    count += len;
    return this;
@Override
public StringBuilder insert(int index, char[] str, int offset,
                            int len)
{
    super.insert(index, str, offset, len);
    return this;
}

主要把index位置以后的字符串后移,然后str拷贝到index位置。

其他的方法就先不写在这里,很多都是一些System.arraycopy的操作。