美文网首页Java程序员
Java Collection 源码之 ArrayList

Java Collection 源码之 ArrayList

作者: AlienPaul | 来源:发表于2020-10-10 17:18 被阅读0次

    ArrayList介绍

    ArrayList内部使用Object[] elementData数组作为数据载体,具有非常好的访问性能,但是随机插入数据的性能不好(涉及到扩容和数组元素移动)。适合用在插入少访问多的环境。

    ArrayList的成员变量

    成员变量的解释在代码注释中标出,如下所示:

    /**
     * Default initial capacity.
     */
    // 默认容量为10,如果创建ArrayList时没有指定容量,则使用默认容量
    private static final int DEFAULT_CAPACITY = 10;
    
    /**
     * Shared empty array instance used for empty instances.
     */
    // 空数组。如果ArrayList内没有元素,使用这个常量作为数据载体。所有无元素的ArrayList共享这个常量
    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.
     */
    // 和EMPTY_ELEMENTDATA类似,在第一个元素加入需要扩容的时候用于计算数组长度,和EMPTY_ELEMENTDATA区分
    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.
     */
    // 数据载体,如果它的值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,添加第一个元素后会被扩容为DEFAULT_CAPACITY
    transient Object[] elementData; // non-private to simplify nested class access
    
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    // ArrayList目前存放的元素数量
    private int size;
    

    ArrayList的构造方法

    ArrayList有3个构造方法,支持创建时指定初始容量,支持从已知Collection对象创建ArrayList

    // 指定初始容量的构造方法
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            // 如果初始容量大于0,创建一个容量为初始容量的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 如果初始容量等于0,使用EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    // 默认无参构造函数,使用默认的容量
    public ArrayList() {
        // 使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    // 使用另一个集合创建ArrayList
    public ArrayList(Collection<? extends E> c) {
        // 将另一个集合的数据转换为数组
        elementData = c.toArray();
        // 如果数组大小不为0
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // (1)
            // 如果elementData不是Object类型数组,需要转换成Object类型数组,防止出现bug
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            // 初始化为EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

    注意:(1)这个地方专门进行了类型检查和转换。英文注释说明了需要进行类型转换的原因:c.toArray()可能返回的不是Object[]类型。

    为了证明这个判断的重要性,我们看下Arrays类的asList方法:

    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
    

    如果你认为这个ArrayList是本篇所说的java.util.ArrayList那就大错特错了。这里返回的ArrayListArrays的一个内部类。我们看一下它的部分代码:

    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable {
        // ...
        private final E[] a;
    
        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
        // ...
    }
    

    其中a是它的数据载体,是E[]类型而不是Object[]类型。我们再看一下它的toArray()方法:

    @Override
    public Object[] toArray() {
        return a.clone();
    }
    

    返回类型是Object[],看着像是没什么问题。但是我们如果这么使用Arrays.asList(1, 2, 3).toArray();,实际上返回数组类型不是Object[]而是Integer[]

    下面我们分析下如果没有这个if判断和类型转换会存在什么问题。

    我们这么创建ArrayList

    List<Object> list = new ArrayList<>(Arrays.asList(1, 2, 3));
    list.add(new Object());
    

    如果没有类型判断和转换,list中的elementData类型实际为Integer[],在执行第二行add一个Object的时候就会抛出异常。为了避免这个问题存在,ArrayList构造方法中会把任何集合toArray调用返回类型不为Object[]的都转换为Object[]类型数组。

    ArrayList的重要方法

    add方法

    add方法的作用为在数组末端加入一个元素。代码如下:

    public boolean add(E e) {
        // 确保数据载体数组的容量超过size + 1,如果不够需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 下标size的位置防止元素e,然后size自增
        elementData[size++] = e;
        return true;
    }
    

    扩容的逻辑位于ensureCapacityInternal方法:

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

    这个方法又调用了ensureExplicitCapacity方法。不过在这个之前我们先分析下calculateCapacity方法。该方法负责计算数组容量。代码如下所示:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果ArrayList是未指定初始容量方式初始化,返回初始容量和minCapacity的最大值,否则返回minCapacity
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    

    接下来我们分析ensureExplicitCapacity方法:

    private void ensureExplicitCapacity(int minCapacity) {
        // 该变量位于AbstractList类中
        // 用于fail-fast,即如果在遍历list的时候修改了数据,会抛出ConcurrentModificationException
        // 每次对集合的修改都会递增这个变量
        modCount++;
    
        // overflow-conscious code
        // 如果minCapacity大于elementData的长度(即容量),进行扩容操作
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    grow方法负责具体的扩容逻辑,代码如下所示:

    private void grow(int minCapacity) {
        // overflow-conscious code
        // 获取原先的容量
        int oldCapacity = elementData.length;
        // 计算扩容后的新容量,新容量为原先容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新容量比minCapacity小,直接扩容到minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新容量比MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)还大,使用hugeCapacity计算新容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        // 复制elementData内容到新数组,容量为newCapacity,赋值给elementData
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    最后是hugeCapacity方法:

    private static int hugeCapacity(int minCapacity) {
        // 如果minCapacity小于0,说明发成溢出,抛出OutOfMemoryError
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    

    add(int index, E element)方法

    该方法在指定index插入元素。

    public void add(int index, E element) {
        // 检查index是否越界,后面分析
        rangeCheckForAdd(index);
    
        // 数组扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // index及其后面的元素依次向后挪动
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // index位置存放element
        elementData[index] = element;
        // size自增
        size++;
    }
    

    检查index是否越界的逻辑位于rangeCheckForAdd方法:

    private void rangeCheckForAdd(int index) {
        // 如果index超过size或者index为负,抛出异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    

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

    作用为将c的所有元素追加到ArrayList中。

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 将c的元素追加到elementData中
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    

    addAll(int index, Collection<? extends E> c)

    作用为将c的所有元素插入到ArrayList的index位置。

    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;
        elementData index位置之后的元素依次向后移动numMoved个位置
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
    
        // 插入a到elementData的index位置
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    

    remove(int index)方法

    删除指定index的元素。

    public E remove(int index) {
        // 检查确保index必须小于size
        rangeCheck(index);
    
        // 确保fail-fast
        modCount++;
        // 取出需要移除的element,赋给oldValue
        E oldValue = elementData(index);
    
        // 计算需要有多少个元素需要移动,即被删除的元素后面还有多少个元素
        int numMoved = size - index - 1;
        // elementData之后的元素依次向前移动
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        index为size地方的元素设置为null,便于GC回收。然后size减1
        elementData[--size] = null; // clear to let GC do its work
    
        // 返回被移除的元素
        return oldValue;
    }
    

    remove(Object o)

    从ArrayList中移除o对象。如果成功移除返回true,否则返回false。

    public boolean remove(Object o) {
        if (o == null) {
            // 如果o是null,遍历查找第一个为null的元素,调用fastRemove移除,返回true
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            // 如果o不为null,遍历找到第一个相同的元素,调用fastRemove移除,返回true
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        // 如果没有移除任何元素,返回false
        return false;
    }
    

    真正删除元素的逻辑位于fastRemove,逻辑如下所示:

    private void fastRemove(int index) {
        // fail-fast
        modCount++;
        // 和remove(int 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
    }
    

    相关文章

      网友评论

        本文标题:Java Collection 源码之 ArrayList

        本文链接:https://www.haomeiwen.com/subject/essjpktx.html