【Java集合框架源码分析(JDK1.7)】-ArrayList源码分析

ArrayList概述

  • ArrayList是List接口的可变数组的实现,与Java中的数组相比,它的容量能动态增长,动态增长内存。
  • ArrayList实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,还提供了一些方法来操作内部用来存储列表的数组的大小。
  • ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

《【Java集合框架源码分析(JDK1.7)】-ArrayList源码分析》

  1. ArrayList 继承了AbstractList抽象类并且实现了List接口,所以提供了数组相关的添加、删除、修改、遍历等功能;
  2. ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。也就是说,可以通过索引来快速获取元素对象;
  3. ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆

ArrayList的实现

一、 私有属性

    /**
     * 初始容量为10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 调用无参构造函数初始化的空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 保存数据的数组       
     */
    private transient Object[] elementData;

    /**
     * 数组大小
     */
    private int size;

二、 ArrayList 提供了三种方式的构造器

  1. public ArrayList()可以构造一个默认初始容量为10的空列表;
  2. public ArrayList(int initialCapacity)构造一个指定初始容量的空列表;
  3. public ArrayList(Collection
    /** * 带容量大小的构造函数。 */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /** * 无参构造函数。默认容量是10。 */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

    /** * 传入一个Collection,则将Collection里面的值copy到arrayList */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }   

三、 元素存储

ArrayList中提供了set(int index, E element)add(E e)add(int index, E element)addAll(Collection <? extends E>c)addAll(int index,Collection<\? extends E> c)多种添加元素的方法,下面将分别进行讲解:

  • set(int index, E element)
    /** * 用指定位置元素替代原位置元素,并返回当前位置元素 */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }
  • add(E e)

    /** * 将指定的元素添加到此列表的尾部 */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
    // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的 
        modCount++;

        //超出数组可容纳的长度后需要对数组进行动态扩容!!! 
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /** * 这才是动态扩展的核心,扩容的规则:先确定容量大小,再申请空间并拷贝元素 */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //通过右移一位方式设置新数组的容量扩展为原来数组的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // copyOf包括申请空间和拷贝元素操作
        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;
    }

数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量

  • add(int index, E element)
    /** * 将指定的元素插入此列表中的指定位置。如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。 */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
       // 将 elementData中从Index位置开始、长度为size-index的元素,拷贝到从下标为index+1位置开始的新的elementData数组中。即将当前位于该位置的元素以及所有后续元素右移一个位置。 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    /** * 比set方法多了索引不能小于0条件 */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • addAll(Collection<\? extends E> c)
    /** * 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此ArrayList的尾部。 */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
  • addAll(int index, Collection<? extends E> c)
    /** * 从指定的位置开始,将指定collection中的所有元素插入到ArrayList中。 */
    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;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

四、 删除元素

  • remove(int index)
    /** * 首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。 */
    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; // clear to let GC do its work

        return oldValue;
    }
  • remove(Object o)
    /** * 移除此列表中首次出现的指定元素(如果存在)。 */
    public boolean remove(Object o) {
        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; // clear to let GC do its work
    }

五、 查找元素

    /** * 返回此列表中指定位置上的元素。 */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

     @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

六、 fail-fast机制

  • ArrayList是线程不安全的,如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
  • 这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对ArrayList内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

七、 扩容策略

  • ensureCapacity(int minCapacity)

    1. 数组在扩容时会将老数组元素拷贝到新的数组中,每次扩容会是原有长度的1.5倍,代价是很高的,所以实际应用时候应尽量避免扩容,可以预先估计好数组容量并在初始化时候就指定容量,降低频繁扩容的开销,也可以通过ensureCapacity方法手动指定ArrayList实例的容量,以减少递增式再分配的数量

    2. 在数组扩容的同时通过调用Arrays.copyOf实现元素的拷贝,而Arrays.copyOf底层是通过System.arraycopy接口实现数组元素的拷贝。在对ArrayList指定位置添加元素或者删除元素时都可能涉及到元素的移动,移动元素都是通过System.arraycopy接口来实现的,从本质来说元素移动就是从一个位置拷贝到另一个位置。System.arraycopy的实现是本地方法调用,最底层调用的方法类似memmove,所以不需要担心在拷贝自身时会出现元素覆盖的情况。

    /** * 扩容需要考虑数组是否为空实例,如果是空实例,则需要考虑minCapacity和DEFAULT_CAPACITY的大小关系。起码需要构造DEFAULT_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;
//minCapacity<minExpand的情况为实例已经申请默认大小的数组,暂时足够存储元素
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
  • trimToSize()
    避免出现size很小但elementData.length很大的情况造成的空间浪费
    /** * 返回一个新的数组给elementData,元素内容保持不变,length和size相同,节省空间。 */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

小结

  1. ArrayList的默认初始容量为10,它的容量会随着元素的增加而自动增长。每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容后的容量为之前容量的1.5倍,并且要进行数组拷贝操作。所以如果提前知道所需要的容量,在使用ArrayList时,可以在构造时给ArrayList指定一个初始容量,这样就可以减少扩容时的拷贝操作。
  2. ArrayList在添加和删除方面,除了在末尾添加或删除元素,其它情况下都需要移动数据。
  3. ArrayList的操作是线程不安全的,它的迭代器是fail-fast的。

参考:http://blog.csdn.net/ochangwen/article/details/50586260

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/elricboa/article/details/78289441
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞