ArrayList源码分析与理解

java.util.ArrayList是最常用的集合类之一,阅读它的源码有助于帮助我们正确的使用它,并且学习它的思想。
在Java中,还有其他的一些集合类,它们的关系如下图
ArrayList源码分析与理解_第1张图片
(图来源:Thinking in Java )
在源码中有一大段注释,介绍了ArrayList的相关信息,总结如下:

  1. 它是List接口的一种可变数组(Resizable-array)的实现
  2. 可以存放任何元素,包括null,可以保存重复的元素
  3. 非线程安全
  4. 可以用for-each遍历集合,但它是快速失效的。
1. ArrayList的类声明
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

RandomAccess, Cloneable, java.io.Serializable为标记接口
RandomAccess:表明其支持随机快速访问
Cloneable:标记其可以调用Object.clone()方法,并按字段复制。ArrayList重写了clone()方法。
Serializable:可以支持序列化。

2. ArrayList中的字段
    // 默认的初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;
    // 空数组,当创建一个容量为0的ArrayList时
    private static final Object[] EMPTY_ELEMENTDATA = {};.
    //默认的空实例数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //元素存储在此数组里,非私有是为了方便内部类访问,使用transient,该字段不会被序列化
    transient Object[] elementData; 
    //元素的实际数量
    private int size;
    //可存储最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList声明了两个空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA用于使用无参构造器,创建未指明初始容量的集合,另一个用于创建初始容量为0的空数组,这会影响到首次向集合添加元素时的扩容。

3. 创建一个ArrayList

3.1 创建一个默认大小的ArrayList

	//1.无参构造器
	public ArrayList() {
	        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	    }
	//2.指定初始化容量的构造器
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        }
        //如果初始为0,则将数组初始化为 EMPTY_ELEMENTDATA 
        else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    //3. 根据给定的Collection集合创建
   	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;
        }
    }

如果使用无参构造器创建一个ArrayList,会初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组,而不是创建一个容量为DEFAULT_CAPACITY的数组,当向ArrayList里添加第一个元素时,会进行扩容,扩容后容量为DEFAULT_CAPACITY,即为10。如果当前容量无法满足需求,会自动扩容。

    //添加铁元素的方法
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        //如果为默认创建的空数组,添加一个元素,会初始化为DEFAULT_CAPACITY:10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    //根据添加元素后所需的最小容量,判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果所需最小容量大于当前数组的容量,进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //右移一位,即乘以1/2,扩容到原容量1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的容量小于所需的最小容量,则将数组扩容为所需的最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果扩容后大于MAX_ARRAY_SIZE,调用hugeCapacity()可以扩容到Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //将原数组的内容复制到容量为扩容后大小:newCapacity的新数组中,其最终调用的是
        //System.arraycopy方法,这是一个native方法
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3.2 通过Arrays.asList()方法创建
实际上,Arrays.asList(T…a)返回的并不是java.util.ArrayList,而是Arrays类中的一个静态内部类,它继承了AbstractList,所以它也是一个Collection,因此可以通过java.util.ArrayList的有参构造器public ArrayList(Collection c)创建。
通过Arrays.asList()创建的ArrayList是一个不可变长的数组,它没有重写基类AbstractList的add(Object o),remove(int index)等方法,调用这些方法会抛出UnsupportedOperationException。

4. ArrayList中的方法

ArrayList共有以下public方法
ArrayList源码分析与理解_第2张图片
ArrayList源码分析与理解_第3张图片
4.1 向集合中添加元素
共有4个添加元素的方法

//在集合最后添加一个元素
boolean add(E e)
//从一个元素添加到指定位置
void add(int index, E element)
//将另一个集合的元素添加到末尾
boolean addAll(Collection c)
//将另一个集合的元素从指定位置开始添加
boolean addAll(int index, Collection c)

boolean addAll(int index, Collection c)方法的源码如下:

public boolean addAll(int index, Collection<? extends E> c) {
		//边界检查,index应在[0,size]范围
        rangeCheckForAdd(index);
        //将集合转为数组,最终通过System.arraycopy实现
        Object[] a = c.toArray();
        int numNew = a.length;
        //确认所需容量
        ensureCapacityInternal(size + numNew);  
        //确定需要移动元素的数量
        int numMoved = size - index;
        //如果需要移动元素,就把原数组从index开始,复制numMoved个元素,保存到目标数组elementData中,将原数组index后的元素一次保存在目标数组的index + numNew后。
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //将新添加的全部元素依次放入elementData中index到index+numNew的位置上
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

4.2 清空集合

    public void clear() {
        modCount++;
        //引用指向null,交由gc负责回收
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

4.3 扩容
当容量不足时,会调用内部的private ensureCapacityInternal方法自动完成扩容。我们也可以进行手动扩容。

 	//手动扩容
    public void ensureCapacity(int minCapacity) {
    //如果当前数组是默认空数组,则minExpand 设为10,用户指定的容量minCapacity>10,会按用户指定的容量扩容
    //如果当前数组不是默认空数组,即数组中有元素,或创建了容量为0的list,则用户指定的容量大于0才会执行后续语句。
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0: DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
    //在ensureExplicitCapacity还会进行检查,minCapacity大于当前保存元素数组的长度才会扩容
            ensureExplicitCapacity(minCapacity);
        }
    }

4.4 缩容
ArrayList的容量是大于等于其中保存元素的数量,可以把它的容量缩减到和保存的元素数量相同,以节省内存空间。尽量确保不再向其添加元素,避免缩容再扩容这种操作。

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

4.5 从集合中获取元素
ArrayList是基于数组实现的,可以通过索引直接取得该位置的元素,时间复杂度O(1),ArrayList在随机访问有较好的性能,但是在中插入或移除元素,需要进行元素的移动,所以性能较差。LinkedList是由双链表实现的容器,在其中插入或移除元素性能较好,随机访问性能较差。

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

查找某个对象在集合中的位置

    public int indexOf(Object o) {
    	//ArrayList允许加入null,并且允许重复的元素,此方法会从前向后遍历,返回第一个该元素的索引,lastIndexOf会从后向前遍历,返回该元素出现的最后位置,
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
        //线性查找,根据equals方法判断是否为同一个对象
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

4.6 迭代器 iterator
ArrayList 实现了List接口,List继承Collection接口,collection继承了Iterable接口,实现Iterable接口的类都要实现一个能够返回迭代器对象Iterator的iterator();方法。Iterator接口有4个方法
ArrayList源码分析与理解_第4张图片
forEachRemaining 是jdk1.8中的新增方法,具有默认实现。
remove方法的默认实现会抛出UnsupportedOperationException(“remove”);。
hasNext():判断是否还有下一个元素。
next():返回下一个元素。
首先看ArrayList中对Iterable接口iterator()的实现,它返回了Itr对象,这是ArrayList的内部类,实现了Iterator接口。通过这个方法返回的迭代器是fail-fast快速失效的,并且它只能单向移动。ListIterator listIterator()返回一个双向移动的迭代器。

	public Iterator<E> iterator() {
        return new Itr();
    }

Itr的代码如下:

    private class Itr implements Iterator<E> {
        int cursor;       // 下一个待返回元素索引
        // 返回的最后的元素的索引,初始化为-1,可以理解为指向第一个元素之前
        int lastRet = -1; 
        int expectedModCount = modCount;//将预期修改计数初始化为modCount
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //在获取下个元素是,会进行modCount==expectedModCount的检查,为false会抛出ConcurrentModificationException(),
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //cursor后移
            cursor = i + 1;
            //返回元素
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
            	//移除最新返回的元素,即最后调用next()之后的那个元素
                ArrayList.this.remove(lastRet);
                //索引lastRet位置的元素已被删除,后面的元素会依次前移,下一个待返回的索引是lastRet
                cursor = lastRet;
                //最新返回的元素已不存在,设为-1
                lastRet = -1;
                //删除会修改modCount,需要同步,避免迭代器失效
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

在ArrayList中,modCount属性用来实现fail-fast,当一个方法会对ArrayList的数组的容量进行改变,如trimToSize,ensureExplicitCapacity,或使存储结构发生改变,如add,remove,clear,modCount都会自增,替换操作set方法没有改变结构,所以modCount不会改变。
在for-each语句中,做出会修改modCount的操作,迭代器会失效,抛出ConcurrentModificationException()。

阅读完源码之后,应该知道在什么情况下使用ArrayList集合,在什么情况下使用其他集合,记忆如何正确的使用其中的方法。

你可能感兴趣的:(Java,Core)