Java ArrayList 源码分析

了解 ArrayList 源码及其工作原理

Posted by fyypumpkin on January 30, 2019

正文

ArrayList 在我们的代码中相信一定是很常见的,今天就来看一看 ArrayList 的源码 (不包含 Spliterator 相关的, Spliterator 会在后面单独讲一下),这次博客主要内容直接写在代码注释里面

来看一下 ArrayList 的相关变量

 private static final long serialVersionUID = 8683452581122892189L;
    // todo 默认的大小
    private static final int DEFAULT_CAPACITY = 10;

    // todo 这个空数组用于构造器中传入初始容量 = 0
    private static final Object[] EMPTY_ELEMENTDATA = {};

//   todo  这个数组用于默认构造器,(会使用默认的容量 10)
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//   todo 真实的数组元素
    transient Object[] elementData; // non-private to simplify nested class access

//   todo 数组大小
    private int size;
    
    // todo 最大数组容量 (这里实际上最大的还是 Integer.MAX_VALUE, 为了保证某些虚拟机会在数组中存放一些头信息,所以 -8)
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

看到变量里面有两个空的数组,用处是不一样的,一个是构造器传入容量为 0 的时候使用,另一个是默认构造器初始化使用,默认构造器是有默认容量的,为 10,当第一次使用,发现是默认构造器初始化的就会初始化数组长度为 10

看一下下面三个构造器

// 带初始容量的构造器,可以看到,当传入的容量为 0 的时候,会使用 EMPTY_ELEMENTDATA 进行赋值,否则就至今进行数组的初始化
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
// 默认的构造器,无参数,直接将数组元素设置为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA  
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

// 将另一个集合复制进来的构造器,使用了 System.copyOf 函数,如果集合是空的,那么就使用 EMPTY_ELEMENTDATA   
    public ArrayList(Collection<? extends E> c) {
    // 转换成数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }    

add 和 set 方法

    public boolean add(E e) {
//        todo 确保容量,不足就扩容并且会增加 modCount,在迭代过程中,不允许 add 操作
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void add(int index, E element) {
//        todo 确保 index 合法, index <= size && index >= 0
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
//        todo 调用 arrayCopy 方法法复制 index 后面的元素向后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
    // set 方法并不会改变 modCount, 所以在使用迭代器迭代的时候,可以通过 set 修改数据(涉及到数组长度的操作都会修改 modCount)
    public E set(int index, E element) {
        rangeCheck(index);
//        todo 对数组制定下表的元素重新赋值,返回老元素,不会改变迭代器 modCount 状态
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

在上面的 add 方法中,有一个 ensureCapacityInternal 方法,该方法主要用于扩容的判断


    private void ensureCapacityInternal(int minCapacity) {
        // todo 这里就会使用到,当构造器不传和传 0 处理方式是不一样的,如果是空构造,就会使用 DEFAULT_CAPACITY 和 minCapacity 中大的作为初始容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        // overflow-conscious code
        // todo 如果 minCap 大于当前的长度,就需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
//        todo 记录老的长度
        int oldCapacity = elementData.length;
//        todo newCapacity = 1.5 * oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 我认为这里是越界了
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
//        todo 如果大于 MAX_ARRAY_SIZE (Integer.MAX - 8),就会使用 Integer.MAX
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        // todo 调用 copy 函数
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

再来看一下 get 方法,get 方法比较简单,范围判断后直接返回对应数组下标

    E elementData(int index) {
//        todo 直接返回数组的第 index 个元素,不会改变 modCount 状态
        return (E) elementData[index];
    }

    public E get(int index) {
    // 范围检查
        rangeCheck(index);
        return elementData(index);
    }

toArray 方法

     public Object[] toArray() {
//        todo 返回一个新的数组
        return Arrays.copyOf(elementData, size);
     }

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
//        todo 如果传进来的数组长度太小,就会使用新建一个数组
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//        todo 长度够,就在传进来的数组上赋值
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

和删除相关的代码 remove fastRemove clear

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

        modCount++;
        E oldValue = elementData(index);
//        todo 计算需要移动的元素个数
        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;
    }

    public boolean remove(Object o) {
//        todo null 要特殊处理
        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;
    }

 
//    todo 和 remove(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
    }

    public void clear() {
//      todo 清空所有元素,并且 size = 0;
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
    
    // 范围移除
    protected void removeRange(int fromIndex, int toIndex) {
        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;
    }    

看一下 ArrayList 的迭代器

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // 通过游标是否到达 size 来判断有没有下一个元素
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        // 判断 modCount 是否被修改,被修改就会抛出异常
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
                // 游标+1
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        // todo 在迭代器中要使用迭代器中的 remove 方法
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            // todo 这里会检查 遍历中的 modCount 和期望的是否一致,不一致会抛出异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

本文首次发布于 fyypumpkin Blog, 作者 @fyypumpkin ,转载请保留原文链接.