首页 > 技术文章 > ArrayList源码详解

aotemanzhifu 2018-02-27 14:20 原文

ArrayList UML类图

这里写图片描述

ArrayList 概述

ArrayList 是实现 List 接口的动态数组,所谓动态就是它的大小是可变的。实现了所有可选列表操作,并允许包括 null 在内的所有元素。

除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量为 10。随着 ArrayList 中元素的增加,它的容量也会不断的自动增长。在每次添加新的元素时,ArrayList 都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造 ArrayList 时可以给 ArrayList 指定一个初始容量,这样就会减少扩容时数据的拷贝问题。当然在添加大量元素前,应用程序也可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。

请注意,此实现不同步。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(…));

ArrayList 源码分析

几个比较重要的字段

//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//共享的空数组实例。
private static final Object[] EMPTY_ELEMENTDATA = {};
//存放元素的数组
private transient Object[] elementData;

transient是个关键字,这个关键字的意思是:transient修饰的变量将不进行序列化
详解:http://blog.csdn.net/u013207877/article/details/52572975
EMPTY_ELEMENTDATA 和elementData字段的作用将在构造方法里体现

构造方法
    //初始化一个默认为10的容量
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
    //可以指定初始化容量,指定初始容量为负时,则抛出异常
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    //使用集合类对象初始化ArrayList,底层使用数组copy方式。
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        //jdk源码注释: c.toArray might (incorrectly) not return Object[] (see 6260652)
        //意思是:c.toArray可能(错误)不返回Object[]  see 6260652 这个编号代表JDK bug库中的编号
        //原因:https://www.cnblogs.com/cmdra/p/5901793.html
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

add方法

boolean add(E e)
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // jdk源码注释:Increments modCount!!。 自增修改次数!!
        elementData[size++] = e;
        return true;
    }

    //这个方法是 确保内部的容量大小可以存储元素
    private void ensureCapacityInternal(int minCapacity) {
        //if语句判断的是,当前是否第一次添加元素,如果是的话,则把最小容量(minCapacity)赋值为10
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY的值一定是10,final修饰了
        }
        ensureExplicitCapacity(minCapacity);
    }
    //这个方法是 确保明确增加多少容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//防止迭代的时候有其他线程进行结构的改变。

        //如果最小的列表容量跟当前数组的长度相减大于0的话,那么需要进行扩容;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //这个方式是进行容量扩容
    private void grow(int minCapacity) {
        //oldCapacity 旧的容量
        int oldCapacity = elementData.length;
        //newCapacity 新的容量   (oldCapacity >> 1)这个表达式的意思是oldCapacity*1.5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果if语句成立,当minCapacity小于0则会抛出内存溢出异常,当minCapacity大于0,则newCapacity=Integer.MAX_VALUE;            
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //当前元素添加到新的数组,并进行数组扩容。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //数组扩容并把元素添加到新的数组里面
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        //判断类型是否一致,如果不一致,则重新实例化(Array.newInstance())数组
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
                //复制数组,将原数组从0索引开始复制,复制到新的数组,从0索引位置开始复制,复制Math.min(original.length, newLength)个
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
void add( int index, E element)方法
    //指定的位置插入指定的元素,指定的位置必须小于等于size
    public void add(int index, E element) {
        //范围检查,指定的索引不能超过当前的容量值,也不能小于0
        rangeCheckForAdd(index);
        //确保容量内部有位置存储
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将当前数组从指定的索引开始复制,复制到指定索引后面的一个位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    //检查传入的index值是否越界。
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

Remove

    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; // JVM会清除
        //返回移除的元素
        return oldValue;
    }

这里写图片描述

get(int index)

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

trimToSize

底层数组的容量调整为当前列表保存的实际元素的大小的功能

    public void trimToSize() {
        modCount++;
        //如果时间大小小于缓冲区容量的长度,则进行数组复制。
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }
Iterator迭代器
    private class Itr implements Iterator<E> {
        int cursor;        // 返回下一个元素的索引
        int lastRet = -1; // 返回的最后一个元素的索引;如果没有,则为1
        int expectedModCount = modCount;

        public boolean hasNext() {
            //第一次调用,因为变量的初始化,那么cursor的值为0,
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            //检查是否有被并发修改
            checkForComodification();
            //将光标赋值给i
            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];
        }

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

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

推荐阅读