ArrayList源码解析

116 阅读5分钟

本文主要对ArrayList源码做一次解析,以及个人的理解做一次记录

ArrayList的实现

通过对源码的阅读可以发现,ArrayList内部的所有操作,都是对内部初始化的 Object [] 的操作。这意味着对内部数据的查询相对较快速,因为数组有角标可以快速访问,相对的对数据进行增、删效率就没那么高了(数组定长,对数据的修改都是一次copy)

里面的代码也相对比较简单,挑如下几个方面说一下主要的实现

ArrayList数据操作主要方法

  1. get(int index);//获取指定位置的数据
  2. set(int index, E element);//将数据设置到指定位置
  3. add(E e);//将数据添加到最后
  4. add(int index, E element);//将数据添加到指定位置
  5. remove(int index);//移除指定位置的数据
  6. remove(Object o);//移除指定的数据
  7. clear();//清除所有数据
  8. addAll(Collection<? extends E> c);//将整个集合添加进去
  9. addAll(int index, Collection<? extends E> c);//从指定位置开始添加整个集合
  10. removeRange(int fromIndex, int toIndex);//删除指定范围的数据
  11. retainAll(Collection<?> c);//保留两个list的交集

ArrayList对内部数据的操作主要有如上10个方法。在操作几个添加数据相关方法时会对当前的数组进行容量判断,如果不够则进行扩容,默认是增加当前容量的一半。

对数据的增删主要通过System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);实现。

System.arraycopy这个方法表示将src这个数组的数据从srcPos开始一共length长度的数据,复制到dest这个数组的destPos位之后。

ArrayList的扩容

先看如下这几个方法,实现的ArrayList扩容

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

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == 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;
    }

每次增删数据前都会判断当前数组的容量,判断的方法主要是ensureCapacity()和ensureCapacityInternal(),一个公开一个内部调用,调用之后如果数据容量不够都会调用grow()做扩容处理。

来看一下grow()代码实现,添加了注释。

    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);//数据重新拷贝赋值
    }

方法分析

看一下如下几个主要的方法

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//这里相当于将数据做了一次移动,将index之后的数据统一往后移动了1位
        elementData[index] = element;//同时将index的数据进行了重新复制
        size++;
    }
    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//同add方法,这里将index之后的数据统一往前移动了1位
        elementData[--size] = null; //同时这里是通过复制实现的移动最后一位数据现在是多余的,清除

        return oldValue;
    }
    public boolean remove(Object o) {
    
    //这里对数据做了是否为null的区分,节省了equals的比较
    
        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; //最后一位数据肯定多余
    }
    public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
    }
    protected void removeRange(int fromIndex, int toIndex) {
        // Android-changed : Throw an IOOBE if toIndex < fromIndex as documented.
        // All the other cases (negative indices, or indices greater than the size
        // will be thrown by System#arrayCopy.
        if (toIndex < fromIndex) {
            throw new IndexOutOfBoundsException("toIndex < fromIndex");
        }

        modCount++;
        
        //确定需要移位的数据
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

    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++)
            //这里通过一个boolean值来区分,保留下的数据到底是不是交集
                if (c.contains(elementData[r]) == complement)
                //这里很巧妙了保存了需要的数据
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            
            //这里的if判断是防止上面try里面报异常,将已经修改的数据继续做保存操作
            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. 初始容量为10,每次扩容为当前容量的一半
  2. 内部维护一个size字段用于表示当前数据大小
  3. 主要通过System.arrayCopy()对数据做复制、移位等处理
  4. 由于内部使用数组实现,查询时可以通过脚标查询效率高,跟新数据则效率低