容器源码分析之——ArrayList

前言

本文将通过阅读 JDK 中的 ArrayList 的源码,来全面分析 ArrayList 的一些特性。

2021-01-30更新:回过头来再看这片源码分析文章,觉得两年前的自己读源码,过于追求代码细节。这个方式也使得自己在后面阅读更复杂的源码过程中吃了亏。阅读源码应更多地去体会其整体设计,而不要陷入一些代码细节和技巧中。

一、ArrayList 简介

1.1 类继承结构

1.2 数据结构

二、源码分析

2.1 注释

可调整大小的List接口的数组实现,实现所有可选列表操作,并允许所有元素,包括null。除了实现List接口之外,该类还提供了一些方法来操纵内部使用的存储列表的数组的大小。 (这个类是大致相当于Vector,不同之处在于它是不同步的)。
该size,isEmpty,get,set,iterator和listIterator操作在固定时间内运行。 add操作以摊余常数运行 ,即添加n个元素需要O(n)个时间。 所有其他操作都以线性时间运行(粗略地说)。 与LinkedList实施相比,常数因子较低。
每个ArrayList实例都有一个capacity。 capacity是用于存储列表中的元素的数组的大小。 它总是至少与列表大小一样大。 当元素添加到ArrayList时,其容量会自动增长。 没有规定增长政策的细节,除了添加元素具有不变的摊销时间成本。
应用程序可以添加大量使用ensureCapacity操作元件的前增大ArrayList实例的容量。 这可能会减少增量重新分配的数量。
请注意,此实现不同步。 如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作,或明确调整后台数组的大小;仅设置元素的值不是结构修改。)这通常是通过在一些自然地封装了列表。 如果没有这样的对象存在,列表应该使用Collections.synchronizedList方法“包装”。 这最好在创建时完成,以防止意外的不同步访问列表:
List list = Collections.synchronizedList(new ArrayList(...));
快速失败:iterator和listIterator会返回快速失败,当迭代器被创建之后,该list的结构又被修改时。只有当迭代器自己修改list结构时(通过迭代器自身的remove或add方法)不会返回快速失败。返回快速失败的表现是抛出ConcurrentModificationException。因此,面对并发修改,迭代器的快速干净的失败(抛出异常)而不是冒着未来不确定性隐藏的风险。
请注意,迭代器的快速失败行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。快速失败的迭代器尽力ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。
这个类是Java Collections Framework的成员

2.2 定义

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable

类定义与类继承结构图是对应的。
从中我们可以了解到:

  • ArrayList:说明ArrayList支持泛型。
  • extends AbstractList :继承了AbstractList。AbstractList提供List接口的骨干实现,以最大限度地减少“随机访问”数据存储(如ArrayList)实现Llist所需的工作
  • implements List:实现了List。实现了所有可选列表操作。
  • implements RandomAccess:表明ArrayList支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
  • implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。
  • implements java.io.Serializable:表明该类具有序列化功能。

2.3 域

    /**
     * 初始化默认容量.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定该ArrayList容量为0时,返回该空数组。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其内数据量为0。
     * 它与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存添加到ArrayList中的元素。
     * ArrayList的容量就是该数组的长度。
     * 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入ArrayList中时,数组将扩容至DEFAULT_CAPACITY。
     * 被标记为transient,在对象被序列化的时候不会被序列化
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList的实际大小(数组包含的元素个数)。
     *
     * @serial
     */
    private int size;
  • EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别:
    在调用无参构造函数的时候,返回的是后者,在调用指定了容量为0的构造函数时,返回的是前者;两者的初始容量一个为指定的0,一个为DEFAULT_CAPACITY(默认为10);对于后者,当第一次添加元素时,就会扩容至默认初始容量DEFAULT_CAPACITY。
  • ArrayList的capacity和size的区别:
    Capacity是整个数据结构的大小,size是目前存储了多少元素的大小。
  • 标记为transient的elementData是怎么进行序列化和反序列化的?
    ArrayList自定义了它的序列化和反序列化的方式,详情查看writeObject()和readObject方法。

2.4 构造方法

/**
     * 构造一个指定初始化容量为initialCapacity的空ArrayList
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException 如果初始化容量为负
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
			//指定初始化容量为0,对应的是EMPTY_ELEMENTDATA这个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 构造一个初始化容量为10的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定collection的元素的列表,这些元素的排列是由该collection的迭代器的返回顺序决定的
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     *   */
    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;
        }
    }

共有三个构造函数

  1. ArrayList(int initialCapacity):
  2. ArrayList()
  3. ArrayList(Collection<? extends E> c)

2.5 核心方法

读源码的思路应该是通过核心方法去辐射相关方法,而不是割裂的按照代码顺序去读方法。

2.5.1 get(int index)

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
	 /**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
 * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	 @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

方法步骤:

  1. RangeCheck()判断是否越界;
  2. 直接通过数组下标获取元素
    因为ArrayList的底层实现就是数组,get方法的时间复杂度为O(1)。

2.5.2 add(E e)

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
      /**
     * 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;
    }
	
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
		//如果数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则最小扩容的大小为DEFAULT_CAPACITY
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return 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;

add(E e)涉及到了ArrayList的扩容机制,整个方法的步骤如下:

  1. ensureExplicitCapacity(int minCapacity):modCount++,modCount是父类AbstractList的变量,用于保存数据结构的结构修改次数。当添加一个元素之后的元素个数大于当前数组的容量时,进行扩容:
if (minCapacity - elementData.length > 0)
            grow(minCapacity);
  1. grow(int minCapacity):扩容机制:将现数组的长度增加到1.5倍,然后取所需的长度(minCapacity)和增加到1.5倍的长度的较大值为新数组的长度;在此之前,判断这个新数组长度是否超过了ArrayList所允许的长度最大值即MAX_ARRAY_SIZE(定义private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;)超过的话就采用Integer.MAX_VALUE。

2.5.3 add(int index, E element)

 /**
     * 在指定位置插入元素,然后将当前位置和其往后的元素向后移一位
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
		//1、越界检查
        rangeCheckForAdd(index);

		//2、空间检查,是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
		//arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length):
		//将src从srcPos位置往后的length长度的元素复制到dest中,从destPos位置开始
		//结果就是将index和其往后的元素向后移一位并且空出了indx位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
   /**
     * A version of rangeCheck used by add and addAll.
	 *检查index位置是否合法
   */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

add(int index E e)需要先对元素进行移动,然后完成插入操作,意味着需要O(n)。

2.5.4 remove(int index)

 /**
     * 删除指定位置的元素并返回该元素,并将该位置之后的元素向左移一位
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
		//1、检查index是否越界
        rangeCheck(index);

		//2、结构修改都要在modCount上加1,添加元素的方法这个语句在调用的ensureExplicitCapacity()方法中
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
			//3、将该位置之后的元素向左移一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
		//4、置为null,GC会对这块进行回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

注意:为了让GC起作用,必须显式的为最后一个位置赋null值。上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。
由于删除指定位置元素需要将后面的元素左移一位,因此时间复杂度为O(n)

三、小结

总结几个比较重要的知识点:

ArrayList 的扩容机制

当添加一个元素之后的元素个数大于当前数组的容量时,需要进行扩容。将现数组的长度增加到1.5倍,然后取所需的长度(minCapacity)和增加到1.5倍的长度的较大值为新数组的长度;在此之前,判断这个新数组长度是否超过了ArrayList所允许的长度最大值即MAX_ARRAY_SIZE(定义private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;)超过的话就采用Integer.MAX_VALUE

ArrayList 的读写特性

  1. 对于读操作,get,O(1)的复杂度。
  2. 对于写操作,add 或者 remove,在指定特定位置时,由于需要移动数组中的元素(移动元素依赖system.arraycopy方法,该方法比较耗时),会使得写入速度较慢。
  3. 另外,ArrayList 是线程不安全的。
posted @ 2021-01-30 15:24  CleverZiv  阅读(66)  评论(0编辑  收藏  举报