全网最详细的--ArrayList扩容机制与底层原理

上一篇中我们了解到,ArrayList底层是基于数组实现的,并且通过add()方法实现元素的增加。

全网最详细的--ArrayList与LinkedList的区别与耗时对比_Petter's Blog的博客-CSDN博客

但是我们知道数组初始化时都是指定元素个数,或者指定数组大小,而ArrayList既然是基于数组实现的,为什么没有初始化大小,也没有指定元素,也可以用呢???

/*数组的初始化方式*/
int[] arrays = new int[3];  //第一种
int[] arrays1 = {1,2,3};    //第二种

而且为什么通过add()方法就可以往里面增加元素呢???

以往的数组可是指定了大小,就不可以再变了,ArrayList是怎么实现动态的改变数组大小呢???

而且为什么可以一直往里面add,ArrayList没有边界吗???

一、ArrayList的初始化

第一种构造方法

List<String> list = new ArrayList<>();    //无参的构造方法

ArrayList中有一个默认容量,默认在初始化时的容量大小为10,并且调用了一个无参的构造函数,将“数组”内的元素赋值。

private static final int DEFAULT_CAPACITY = 10;    //默认初始大小为10

transient Object[] elementData;    //ArrayList中的元素集合

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    //默认置为空
}

第二种构造方法

List<String> list = new ArrayList<>(2);     //指定容量大小

 指定容量大小进行初始化,如果大于0,则分配一个对应大小的Object数组;如果小于0,则分配一个空数组,相当于无参的构造函数;除此之外,抛出异常。

private static final Object[] EMPTY_ELEMENTDATA = {};    //空数组

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {    //如果初始容量大于0,则分配一个对应大小的Object的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;//如果等于0,则等于一个空数组
    } else {    //否则报错                           
        throw new IllegalArgumentException("Illegal Capacity: "+
                initialCapacity);
    }
}

通过以上两种构造方法,实现了ArrayList的初始化,即指定数组容量大小,将数组元素值设置为空数组。 

二、add()方法的实现

1、将数组大小加1

2、将元素赋值到最后一位数组下标上

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 修改数据大小,+1
    elementData[size++] = e;    //将最后一个下标赋值为新增元素
    return true;
}

三、动态扩容

修改数组大小前,先对容量大小进行校验,如果新增前,是个空数组,则还是采用默认容量大小:10,如果数组内已经有值,则采用 (当前容量+1) 后的容量大小。

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //如果新增前,是个空数组,则还是采用默认容量大小:10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

 grow()为ArrayList的扩容方法,在扩容前,首先判断当前容量大小是否已经超出限额。比如初始化容量大小为10,当新增第二个元素时,minCapacity为3,这是3-10 < 0,则不进行扩容,还维持初始化的容量;否则,则需要扩容。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0) //如果新增后的容量小于限额容量,则不进行扩容
        grow(minCapacity);    //否则扩容
}

 扩容大小默认为当前容量的1.5倍。

比如当前容量大小为10,则新的容量大小:10 + (10>>1) = 15。然后将原来的10个元素、以及新增的第11个元素放入到这15个空间中。

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容默认为当前容量的1.5倍

    //如果扩容后的容量大于所需要的容量,则扩容完成,之后进行最后一步的拷贝
    if (newCapacity - minCapacity < 0) 
        newCapacity = minCapacity; //如果扩容之后的容量小于当前容量,则不进行扩容(用于数组为空时,第一次add元素)

    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

当然,ArrayList的最大容量也有一个限额,当超过默认的最大容量后,则会抛出异常。

如果超出最大容量2的31次方,则会变为负数,并抛出异常

//ArrayList的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


//判断是否超出最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);


//如果超出最大容量2的31次方,则会变为负数,并抛出异常
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();    //抛出异常
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

猜你喜欢

转载自blog.csdn.net/A_captain_608/article/details/127212067