集合——ArrayList实现原理分析

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;


    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;


    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};


    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access


    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    ......

}

以上代码来源JDK1.8,对ArrayList的解析代码来源于JDK1.8

ArrayList 的底层最重要的两个属性:Object数组elementData和size属性。

除以上两个属性外还有一些默认的属性值,我就不一一介绍,通过后面的解析,大家就能明白这些属性是用来干什么的。

由此可以看出ArrayList的底层由数组实现,ArrayList中存放的值实际都是放在elementData中。

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
通过观察 ArrayList的构造函数可以知道,elementData初始化时一个空的数组。

add操作过程:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

由官方注释可以看出,ensureCapacityInternal()方法主要用来增加modCount的值。

modCount成员变量是继承于AbstractList的,这个成员变量记录着集合的修改次数,也就是每次add或者remove的时候都会加1。

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

通过代码可以分析得出calculateCapacity()方法的作用是求出当前elementData至少应该给多大。

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

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

ensureExplicitCapacity()方法中对modCount做加1操作。

接着判断满足条件下的elementData长度与当前elementData的长度大小。如果当前elementData长度大于minCapacity,则无需改变elementData长度,反之则需要增加elementData的长度。

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;
    }

grow()方法就是为当前elementData数组增加长度。

int newCapacity = oldCapacity + (oldCapacity >> 1);

由上面这行代码可知,elementData数组长度增长量是当前长度的一半向下取整。如果长度还是不够的话,那么直接增长到满足条件下的最小长度,也就是minCapacity的值。

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

由上面代码可知,数组默认最大长度是MAX_ARRAY_SIZE的值,也就是Integer.MAX_VALUE-8。当超过这个值时就需要判断当前值是否存在int值溢出问题。int的最大值是Integer.MAX_VALUE,当对Integer.MAX_VALUE值做加一时便会出现溢出问题。当newCapacity的值大于安全值即MAX_ARRAY_SIZE时,需要做溢出检查。

此处需要注意的是,做安全校验的不是newCapacity,而是minCapacity。因为当newCapacity大于安全值时,newCapacity也便没有意义了。通过hugeCapacity()方法判断满足条件下的最小长度值也就是minCapacity值是否本身就溢出了,若溢出则抛出数组下标越界异常,若无溢出,则根据值大小返回MAX_ARRAY_SIZE或Integer.MAX_VALUE的值。

确定elementData的新长度的之后调用Arrays.copyof()方法,生成新的elementData数组,并把原有的elementData中的值复制过来。

至此我们就清楚了list新增一个值时,对底层elementData长度控制机制。

这种扩容策略有一个好处,就是在添加大量元素前,通过ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量。

回顾add()方法的源码,接下来应该是

elementData[size++] = e;

准备好elementData的大小后,执行这行代码便是顺理成章的事了。

remove操作过程:

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

由上面两段代码可知,remove操作时,需要先确认移除对象下标,然后通过System.arraycopy方法将下标后的所有值往前复制一个位置,最后将elementData数组最后一个值清空。

get操作:

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

由上面两端代码可知,get方法的操作就两步

第一步:检查下标合法性。

第二标:根据获取下标值。

set操作:

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

由上段代码可知,set方法的操作也就是根据下标替换原来的值操作,流程相当简单。


通过上面对add方法和remove方法的分析,可以看出,ArrayList的新增和删除操作都是通过Object数组的复制来处理数组的变化,size总是记录当前数组的大小。这就解释了,ArrayList添加和删除元素的效率低,原因是数组复制过程消耗资源较多。而查找和更新元素的效率比较高,原因是数组可以由下标直接定位对象位置,获取和更新值。

以上源码来源于JDK1.8。


通过上面对add方法和remove方法的分析,可以看出,ArrayList的新增和删除操作都是通过Object数组的复制来处理数组的变化,size总是记录当前数组的大小。这就解释了,ArrayList添加和删除元素的效率低,原因是数组复制过程消耗资源较多。

猜你喜欢

转载自blog.csdn.net/qq_26629277/article/details/80519624