从jdk1.8源码看ArrayList的扩容原理

ArrayList源码中是用一个数组(Object[] elementData)来保存元素的,自然,三个构造函数都会为elementData赋值。最简单的构造函数如下:

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从注释可以看出, 构造一个初始容量为10的空列表 。那么当放入第十一个元素时,程序猿并没有去主动扩充elementData的数组长度,这个扩容过程是源码完成的。那么,源码是怎么完成扩容的?一次扩容多少?

在ArrayList源码中,方法名带有“add”的方法都会调用ensureCapacityInternal(int minCapacity)方法,ensureCapacityInternal方法是保证数组长度够用。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

在调用add方法时,调用ensureCapacityInternal方法,传入的参数为 size + 1,。size是ArrayList的大小,即ArrayList的实例中含有多少个元素,注意size并不是elementData数组的长度。成员变量size上面的注释The size of the ArrayList (the number of elements it contains).

ensureCapacityInternal方法调用了ensureExplicitCapacity方法,ensureExplicitCapacity方法又调用了grow方法,当minCapacity值大于elementData.length时,又调用了grow方法。在扩容的时候,源码是先获取当前elementData长度,在oldCapacity 基础上加上 oldCapacity 的1/2(oldCapacity >> 1 相当于oldCapacity除以2),这就是扩容ArrayList 扩容1.5倍说法的由来。但是,我们可以看到下面有两个if语句,第一个if表示,当扩容1.5倍时,还是不能满足这次的“add”操作,(这里是考虑addAll方法,一次添加多个元素的情况),就使用minCapacity作为新的数组长度。

第二if 表示当 newCapacity大于MAX_ARRAY_SIZE时,MAX_ARRAY_SIZE的数值为Integer.MAX_VALUE - 8;为什么是Integer的最大值减去8,这个问题的解释比较多,stackoverflow上回答算比较好的解释。我个人认为MAX_ARRAY_SIZE的注释,其实已经回答得差不多了——数组能分配的最大的大小,一些VMs会在这个数组中保留一些header words,试图分配更大值(也就是说分配Integer.MAX_VALUE - 7到Integer.MAX_VALUE 这7个值,严格来说,数组最大的长度只能是Integer.MAX_VALUE)会导致OutOfMemoryError:即请求的数组大小超出VM限制。具体header words指的是什么,有兴趣的话,可以进一步研究学习。

hugeCapacity方法里面的抛出OutOfMemoryError,是可能发生的。因为int的最高位是表示符号的,0表示正数,1表示负数,不妨执行一下System.out.println(Integer.MAX_VALUE + 1)看看会输出什么。

那么讲完扩容,讲讲缩容。如果一个ArrayList实例删除了1/2或更多,会不会减小elementData的数组长度。源码并不会主动缩容,但可以使用 trimToSize 方法缩容。

你可能感兴趣的:(易筋经——Java)