集合框架源码学习——ArrayList

来源:互联网 发布:制作编程语言 编辑:程序博客网 时间:2024/06/15 13:49

本文所用jdk版本是jdk1.7.0_79版本。


一.ArrayList类的定义

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

上面这一段代码是ArrayList类的定义,从定义上我们可以看出,ArrayList 继承了AbstractList抽象类,实现了List接口,Cloneable接口,Seriallizable接口。在这里我要说一点的就是,AbstractList继承了AbstractCollection抽象类,同时也实现了List接口,所以,我不太清楚如果ArrayList如果不实现List接口,会造成什么影响,这里我猜想应该不会造成什么影响,再一次实现List接口,可能是因为为了使意义更加清楚吧。关于AbstractList抽象类与List接口中又有哪些方法,感兴趣的话可以翻源码去看一下,这里就过多做介绍了。


二.ArrayList类的成员变量

    /**     * Default initial capacity.     */    private static final int DEFAULT_CAPACITY = 10;    /**     * Shared empty array instance used for empty instances.     */    private static final Object[] 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 == EMPTY_ELEMENTDATA will be expanded to     * DEFAULT_CAPACITY when the first element is added.     */    private transient Object[] elementData;    /**     * The size of the ArrayList (the number of elements it contains).     *     * @serial     */    private int size;

上面4个是ArrayList的成员变量,从JDK注释我们可以事先了解一下他们的意义。

1.DEFAULT_CAPACITY是默认初始容量,这个默认初始容量的意思就是,ArrayList在创建对象时,默认的容量为10,如果容量不够,后面会有容量的扩展,关于扩展的方法,后面会有。

2.EMPTY_ELEMENTDATA是定义无参实例时,默认的参数集合。

3.elementData,用于存储ArrayList元素的缓冲数组。ArrayList的容量就是elementData数组的长度。任何空的同时elementData与EMPTY_ELEMENTDATA相等的ArrayList当第一个元素添加后,容量都将会扩展至默认容量。这里的emementData被transient修饰,该关键字的意思是该成员变量不用初始化。

4.size,设置ArrayList中包含的元素的数量。


三.ArrayList构造器

    /**     * Constructs an empty list with the specified initial capacity.     *     * @param  initialCapacity  the initial capacity of the list     * @throws IllegalArgumentException if the specified initial capacity     *         is negative     */    public ArrayList(int initialCapacity) {        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];    }    /**     * Constructs an empty list with an initial capacity of ten.     */    public ArrayList() {        super();        this.elementData = EMPTY_ELEMENTDATA;    }    /**     * Constructs a list containing the elements of the specified     * collection, in the order they are returned by the collection's     * iterator.     *     * @param c the collection whose elements are to be placed into this list     * @throws NullPointerException if the specified collection is null     */    public ArrayList(Collection<? extends E> c) {        elementData = c.toArray();        size = elementData.length;        // c.toArray might (incorrectly) not return Object[] (see 6260652)        if (elementData.getClass() != Object[].class)            elementData = Arrays.copyOf(elementData, size, Object[].class);    }
上述代码中可以看到,ArrayList共有三个构造器。

1.ArrayList第一个构造器中,传入一个int类型的参数,用于实例化ArrayList时,初始化elementData数组的长度。

2.ArrayList第二个构造器是一个无参构造器,仅执行一次elementData = EMPTY_ELEMENTDATA

3.ArrayList第三个构造器,传入了一个Collection类型的集合,用于实例化ArrayList时,将参数中的集合加入到实例化的ArrayList中。该构造器中,向几个成员变量进行了赋值,符合了几个成员变量存在的意义。


四.方法详解

4.1 trimToSize()方法

    /**     * Trims the capacity of this <tt>ArrayList</tt> instance to be the     * list's current size.  An application can use this operation to minimize     * the storage of an <tt>ArrayList</tt> instance.     */    public void trimToSize() {        modCount++;        if (size < elementData.length) {            elementData = Arrays.copyOf(elementData, size);        }    }
该方法是用来将ArrayList的容量削减至与长度相等,从源代码中可以看到,所谓削减是将elementData的数组按照实际size的长度重新复制。看到这里的小伙伴相信绝大多数人应该可以理解了吧,其实ArrayList本质上就是用数组存储元素的,而且其值最终就是存储在elementData数组中的。所谓容量,就是指数组的最大长度,所谓size就是指数组中存储的元素的个数。

该方法中还有一个modCount++,这里,modCount变量是继承自AbstractList,我特意到源码中搜了一下,AbstractCollection抽象类与List接口中均没有该参数,可见是AbstractList抽象类中扩展的变量。具体作用可以看注释。

4.2 ensureCapacity(int minCapacity) 方法

    /**     * Increases the capacity of this <tt>ArrayList</tt> instance, if     * necessary, to ensure that it can hold at least the number of elements     * specified by the minimum capacity argument.     *     * @param   minCapacity   the desired minimum capacity     */    public void ensureCapacity(int minCapacity) {        int minExpand = (elementData != EMPTY_ELEMENTDATA)            // any size if real element table            ? 0            // larger than default for empty table. It's already supposed to be            // at default size.            : DEFAULT_CAPACITY;        if (minCapacity > minExpand) {            ensureExplicitCapacity(minCapacity);        }    }
如有必要,增加此ArrayList实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。

源代码中,首先定义一个minExpand,如果ArrayList实例不是空的,则赋值为0,如果是空的,则赋值为默认容量大小。再判断传入参数是否大于minExpand,如果大于,则执行ensureExplicitCapacity(minCapacity)方法。根据源码可知,该方法是一个私有方法,我们可以看一下它的源代码:

    private void ensureExplicitCapacity(int minCapacity) {        modCount++;        // overflow-conscious code        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }

该源代码中,首先判断minCapacity是否大于elementData.length,如果大于,则执行grow(minCapacity)方法。这也是一个私有方法,我们再深入其中来看一下:

    /**     * 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);    }

增加容量,以确保它可以容纳参数所指定的元素的个数。通过我们该方法,终于看到了该功能的底层是究竟如何处理的。

(1)首先定义一个oldCapacity存储原有的元素长度。(2)定义新的容量是旧容量的1.5倍。(3)判断外部传入的参数是否大于新的容量,如果大于,则外部传入的参数就为新的容量,如果不大于,则容量就扩展原容量的1.5倍。(4)判断新的容量是否超过MAX_ARRAY_SIZE(数组最大长度),如果超过,则通过hugeCapacity重新赋值。关于这个方法,做的什么事情,相信小伙伴很容易就能看懂。

总的来说,该方法的的作用是:在不考虑到达最大极限的时候。一般容量扩展最小是原容量的1.5倍,在1.5被仍不满足时,才会直接扩展至参数传入的容量值。


4.2 size()方法

    public int size() {        return size;    }
该方法就是返回size的值,即ArrayList中的元素的个数。


4.3 isEmpty()方法

    public boolean isEmpty() {        return size == 0;    }
该方法就是判断ArrayList是否为空。


4.4 contans(Object o)方法

    public boolean contains(Object o) {        return indexOf(o) >= 0;    }
该方法返回boolean类型,方法体中执行了一个判断indexOf(Object o) >= 0 的方法。下面看一下indexOf方法中的源码:
    /**     * Returns the index of the first occurrence of the specified element     * in this list, or -1 if this list does not contain the element.     * More formally, returns the lowest index <tt>i</tt> such that     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,     * or -1 if there is no such index.     */    public int indexOf(Object o) {        if (o == null) {            for (int i = 0; i < size; i++)                if (elementData[i]==null)                    return i;        } else {            for (int i = 0; i < size; i++)                if (o.equals(elementData[i]))                    return i;        }        return -1;    }
该方法中,可以看出,是循环执行检索,将 o 同 elementData数组循环比较,如果有相等的,则返回该索引。如果没有,则返回-1。它的上一层即可判断出ArrayList是否包含 o 参数的值。

4.5 lastIndexOf(Object o)方法

    /**     * Returns the index of the last occurrence of the specified element     * in this list, or -1 if this list does not contain the element.     * More formally, returns the highest index <tt>i</tt> such that     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,     * or -1 if there is no such index.     */    public int lastIndexOf(Object o) {        if (o == null) {            for (int i = size-1; i >= 0; i--)                if (elementData[i]==null)                    return i;        } else {            for (int i = size-1; i >= 0; i--)                if (o.equals(elementData[i]))                    return i;        }        return -1;    }
返回此ArrayList中最后出现 o元素的索引,如果没有,则返回-1。

源码中,可以看出来这里是以倒序的形式进行查询,与indexOf功能类似,只是查询顺序颠倒。


4.6 clone()方法

    /**     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The     * elements themselves are not copied.)     *     * @return a clone of this <tt>ArrayList</tt> instance     */    public Object clone() {        try {            @SuppressWarnings("unchecked")                ArrayList<E> v = (ArrayList<E>) super.clone();            v.elementData = Arrays.copyOf(elementData, size);            v.modCount = 0;            return v;        } catch (CloneNotSupportedException e) {            // this shouldn't happen, since we are Cloneable            throw new InternalError();        }    }
这里从单词意思和源码都可以看出,它其实是做了一次复制,将原ArrayList赋值一份,并且赋值出来的是一份将modCount归0的ArrayList。


4.7 toArray() 方法

    /**     * Returns an array containing all of the elements in this list     * in proper sequence (from first to last element).     *     * <p>The returned array will be "safe" in that no references to it are     * maintained by this list.  (In other words, this method must allocate     * a new array).  The caller is thus free to modify the returned array.     *     * <p>This method acts as bridge between array-based and collection-based     * APIs.     *     * @return an array containing all of the elements in this list in     *         proper sequence     */    public Object[] toArray() {        return Arrays.copyOf(elementData, size);    }
该方法是将ArrayList转化成数组。


4.8 toArray(T[] a)方法

    @SuppressWarnings("unchecked")    public <T> T[] toArray(T[] a) {        if (a.length < size)            // Make a new array of a's runtime type, but my contents:            return (T[]) Arrays.copyOf(elementData, size, a.getClass());        System.arraycopy(elementData, 0, a, 0, size);        if (a.length > size)            a[size] = null;        return a;    }
该方法与上述类似,增加了一个泛型。


4.9 get(int index) 方法

    /**     * Returns the element at the specified position in this list.     *     * @param  index index of the element to return     * @return the element at the specified position in this list     * @throws IndexOutOfBoundsException {@inheritDoc}     */    public E get(int index) {        rangeCheck(index);        return elementData(index);    }
代码中可以看出,该方法返回index位置的元素。在返回之前执行了一次判断,如果index >=size,则抛出IndexOutOfBoundsException,这里没有做index为负的判断,而是在获取数组值的时候,抛出ArrayIndexOutOfBoundsException,个人感觉这里也可以同时做index < 0的判断。


4.10 set(index, E element)方法

    public E set(int index, E element) {        rangeCheck(index);        E oldValue = elementData(index);        elementData[index] = element;        return oldValue;    }
该方法是将ArrayList的index索引位的值,替换成element元素,并且返回老的元素。


4.11 add(E e)方法

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

该方法是向ArrayList中添加一个元素。

从源码中可以看出,在添加之前,首先执行了一次ensureCapacityInternal(size + 1) 方法,我们在前面介绍过类似的一个方法,而该方法的内部也调用了上面介绍过的那个方法,在这里就不再详细描述了,总之是在添加元素之前,判断是否需要扩展容量,如果有必要的话,就扩展一次容量。

该方法我们可以看出,在添加元素的时候,代码内部自动帮我们判断和扩展容量,不需要我们人工去做。


4.12 (1) remove(int index)方法

    public E remove(int index) {        rangeCheck(index);        modCount++;        E oldValue = elementData(index);        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        return oldValue;    }

该方法是用于删除索引位置的元素。

首先判断index值是否合法,否则抛出IndexOutOfBoundsException;然后执行删除,size减去1,最后返回删除的元素。

这里我们可以看到它删除的过程,实际上是将index+1索引位以及后面的所有元素,向前覆盖一位,再将最后一位置为null。这种方式比较低效,所以如果List需要不断地执行插入删除等操作,不建议使用ArrayList。


4.12(2) remove(Object o)

    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;    }
从代码中可以看出,该方法是执行删除o元素第一次出现的位置,如果删除成功,则返回true。删除方法为faseRemove(int index),并没有调用上面的remove(int index)方法,我们可以看一下该方法的源码:

    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相比,少了个校验,少了个返回删除的元素。因为调用此方法之前,已经执行了校验,不用重复校验,并且删除的元素就是参数o元素,所以也不用返回。所以fastRemove比remove少了两个环节,节省了一定的消耗。


4.13 clear() 方法

    public void clear() {        modCount++;        // clear to let GC do its work        for (int i = 0; i < size; i++)            elementData[i] = null;        size = 0;    }
代码中可以看出,该方法执行一次循环,将elementData数组循环置为null,将size赋值为0。


4.14(1) addAll(Collection<? extends E> c)方法

    public boolean addAll(Collection<? extends E> c) {        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount        System.arraycopy(a, 0, elementData, size, numNew);        size += numNew;        return numNew != 0;    }
该方法是将参数传入的集合一次性全部加入到此ArrayList中。

代码中可以看到,首先将参数中的集合c转为数组,必要的话扩展一次容量,再将参数转过的数组赋值到elementData中。


4.14(2)addAll(int index, Collection<? extends E> c)方法

    public boolean addAll(int index, Collection<? extends E> c) {        rangeCheckForAdd(index);        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount        int numMoved = size - index;        if (numMoved > 0)            System.arraycopy(elementData, index, elementData, index + numNew,                             numMoved);        System.arraycopy(a, 0, elementData, index, numNew);        size += numNew;        return numNew != 0;    }
该方法中比上一个方法,多了一个index参数,我们可以从代码上去理解。

首先执行一次index校验;再将集合c转化为数组a;下面是如果有必要,执行一次扩容;下面就是关键的执行插入的两点:(1),将index后面的元素向后挪,挪的位置就是c的长度;(2)再将转化的数组a,赋值到第一步腾出来的位置。

这里可以看出,该方法执行的插入,也是十分低效的,因为数组本身的原因,所以,这里也可以得出上面的结论,如果执行插入和删除是在List中间做的话,建议不要用ArrayList。


4.15 removeAll(Collection<?> c)

    public boolean removeAll(Collection<?> c) {        return batchRemove(c, false);    }
该方法体中只执行了一次batchRemove(c,false)方法,可以看一下该方法的源码,做了哪些事情:

private boolean batchRemove(Collection<?> c, boolean complement) {        final Object[] elementData = this.elementData;        int r = 0, w = 0;        boolean modified = false;        try {            for (; r < size; r++)                if (c.contains(elementData[r]) == complement)                    elementData[w++] = elementData[r];        } finally {            // Preserve behavioral compatibility with AbstractCollection,            // even if c.contains() throws.            if (r != size) {                System.arraycopy(elementData, r,                                 elementData, w,                                 size - r);                w += size - r;            }            if (w != size) {                // clear to let GC do its work                for (int i = w; i < size; i++)                    elementData[i] = null;                modCount += size - w;                size = w;                modified = true;            }        }        return modified;    }
上面的方法逻辑有点复杂,我们来一点一点剖析。

(1)首先执行一次final Object[] elementData = this.elementData;这里就是定义一个局部变量,不在ArrayList本身上修改,如果出现异常则不会影响到原数据。

(2)执行一次for循环,这里的作用就是循环执行contains方法,将elementData中所有不能在c中找到的元素从0的位置开始重新赋值给elementData。这样能保证的是,所有不需要删除的元素,都被移到了最前面,w就是不需要删除的元素的个数。

(3)接下来将elementData中,w以后的元素置为null,size重新赋值成w。

分析到这里,相信大家已经知道整个方法的作用了,就是将ArrayList中包含了c集合中的元素删除掉,并且我们可以看出来,该方法也是非常低效的,因为删除元素的同时,对于保留的所有元素执行了大量的重新赋值。



由于时间问题,暂时先写一半,后续将在晚一些时间补上