ArrayList扩容原理

众所周知,ArrayList是底层是使用数组这一数据结构来实现的,其扩容也是在数组的基础上扩容。从jdk8源码的角度来分析其扩容机制。
先看一下ArrayList的结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	 /**
     * 默认初始化容量=10.
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 共享的无参构造使用的空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
    * 共享的有参构造使用的空数组
    */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
    * 实际存放数据的数组
    */
    transient Object[] elementData; // non-private to simplify nested class access
    /**
    * 长度
    */
    private int size;
    /**
    * 数组的最大容量 = Integer最大值-8
    */
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    /**
    * 有参数的构造方法
    */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
}

这里说一下比较多的无参数的add方法,添加之前,先检查list内数组的容量,同时传一个 当前容量+1 作为添加后的最小容量。

/**
     * 无index参数时,默认追加到list结尾.
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

再看这个方法的实现:

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

首先计算一下数据容量,如果之前是空数组,则初始化容量等于10.

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 之前是空数组(地址值比较)时,第一次添加元素会初始化一个size=10的数组。 
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 其他情况返回最小容量
        return minCapacity;
    }

确定完期望的最小数组容量以后,然后检查是否需要扩容。

private void ensureExplicitCapacity(int minCapacity) {
		// modCount 是AbstractList里的记录底层数据结构变更的次数,是给迭代器使用的。
        modCount++;
        // 如果要求的最小容量 比当前数组的容量大,就需要扩容。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

该方法中,如果最小容量比当前的数据容量(第一次初始化为10)大,会调用grow方法进行扩容。

private void grow(int minCapacity) {
        // 扩容代码
        int oldCapacity = elementData.length;
        // 新容量 = 旧容量 + 旧容量的一半(右移一位),即1.5倍。第一次扩容后容量=15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 新容量比最小容量小时,新容量=最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 新容量比 数组最大容量大时,调用 hugeCapacity,可能会抛OOM,一般不会有这么大的容量。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity一般等于size+1
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

确定完新容量以后,就调用操作系统提供的数组native的Copy方法,数组的指针是一块连续的内存地址。好处是操作系统直接从一个地址开始复制若干字节到新地址,比使用java方法遍历复制快的多。

同理,通过index进行add的方式,比add多了一个index之后的数据的复制操作。

/**
     * 有index参数时,在指定位置插入
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }