深入理解ArrayList源码(Ⅰ)

本文以JDK8下的 java.util.ArrayList为基础,分析并简单说明ArrayList常用方法的实现思路。以备自用。

这里给出JavaSE8 在线API的网址:

https://docs.oracle.com/javase/8/docs/api/

 

目录

概述

ArrayList的底层结构

类层次关系

源码详析

ArrayList属性源码

ArrayList构造方法源码

ArrayList常用方法源码


概述

在开始正式的学习前,我们先通过源码来了解ArrayList的继承和实现关系

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

显然,ArrayList<E>继承了AbstractList<E>类,并实现了List<E>,RandomAccess,Cloneable,Serializable接口

AbstractList<E>

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

此类提供List接口的骨干实现,从而最大限度地减少了实现由”随机访问“数据存储(如数组)支持的接口所需的工作。对于连续的访问数据(如链表),应优先使用AbstractSequentialList。

简单的说,它是一种适用于随机访问数据存储的List接口实现方式,为ArrayList的实现提供了模板。

List<E>

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.(by JavaSE8 API)

简单的说。List是Collection根接口的一个子接口。表示一种有序集合,也可称为序列。

RandomAccess

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.(by JavaSE8 API)

简单的说。RandomAccess是一种用于列表实现的标记接口,表示他们支持快速的随机访问。

Cloneable

A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.(by JavaSE8 API)

简单的说。实现Cloneable后说明该类具有克隆的能力,保证该类可以正确的调用clone方法。

Serializable

Serializability of a class is enabled by the class implementing the java.io. interface. Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.(by JavaSE8 API)

简单的说。Serializable接口标识了该类具备序列化和反序列的能力。即可通过java.io包下的支持类进行序列化和反序列化。

 

ArrayList的底层结构

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

以上是JavaSE8中关于ArrayList的部分源码,这标示了ArrayList的底层结构——即一个Object类型的一维数组。

清楚了这一点,就不难理解ArrayList相较于LinkedList的区别,也为后文中对于ArrayList方法的详析提供了一些帮助。

 

类层次关系

通过eclipse的显示类型层次关系的功能,得到ArrayList<E>的类层次关系图。如下


其中各种符号及颜色表征的意义如下

颜色

绿色  public

黄色  protected

红色  private

蓝色  default

形状

实心  method

空心  variable

字母标识

C constructor

S  static

F  final

A  abstract

具体的组合

 private  函数

 private  变量

 protected  函数

 protected 变量

 public  函数

 public  变量

default  方法

default  属性

throws  可能抛出异常的函数

  synchronized  线程同步函数

  class  类

  main  包含程序入口的类

constructor  构造器

继承至父类的方法 

相较于API或列表,类层次结构图可视化程度更高。

且此层次结构图可以帮助复查,如果后文中遇到不熟悉的方法或属性,可以回到这里进行。

 

源码详析

下面我们正式开始分析ArrayList底层源码。笔者将学习过程分为三个层次

  • ArrayList属性源码详析
  • ArrayList构造方法源码详析
  • ArrayList常用方法源码详析

 

ArrayList属性源码

ArrayList中包含的常见属性如下

下面我们通过源码来认识这几个属性,因为属性较为简单,所以笔者只进行简单介绍。

DEFAULT_CAPACITY

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

DEFAULT_CAPACITY表示ArrayList底层数组的默认容量,默认值为10。

 

DEFAULTCAPACITY_EMPTY_ELEMENTDATA

/**
  * Shared empty array instance used for default sized empty instances. We
  * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
  * first element is added.
  */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的实例数组。

 

EMPTY_ELEMENTDATA

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

EMPTY_ELEMENTDATA同样也是一个空的实例数组,它与DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别,笔者将会在后文讲解ArrayList的构造方法的过程中进行区分。

 

MAX_ARRAY_SIZE

/**
 * 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;

MAX_ARRAY_SIZE规定了ArrayList底层数组的最大长度,相当于Integer.MAX_VALUE - 8。

 

elementData

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

elementData就是ArrayList底层用于存放元素的Object类型数组。

 

size

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
 */
private int size;

size表示ArrayList中实际存储的元素数量,注意它与容量capacity的区别。(容量表示数组所占空间,而size则代表实际存储的元素个数。)

 

ArrayList构造方法源码

ArrayList中包含的构造方法如下

下面我们结合源码来介绍这三种构造方法的区别

ArrayList()

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

如果通过ArrayList()方法构造一个ArrayList,那么将会把这个空数组的引用赋值给elementData。

注意这里有个需要注意的地方:通过ArrayList()产生实例时,底层数组elementData并不会真的拥有10的容量,而仍是一个空数组。elementData真正的拥有空间是在第一次使用add方法时,关于这一点。将在本文分析add方法源码时给出。

 

ArrayList(int initialCapacity)

/**
 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
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);
    }
}

ArrayList(int initialCapacity)是带参的构造方法,参数initialCapacity是指定的初始化容量。

当initialCapacity > 0时,将会使elementData获得一个容量为initialCapacity的Object类型数组;

当initialCapacity == 0时,elementData将会获得EMPTY_ELEMENTDATA。即一个空数组。这里就区分于默认初始化的DEFAULTCAPACITY_EMPTY_ELEMENTDATA。虽然两者都是空数组,但表示的意义并不相同。

DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示一个默认初始化时赋给elementData的空数组,当其第一次执行add操作时,会为其分配一个容量为10的空间。所以虽然它是一个空数组,在ArrayList中,它代表的Capacity为10。

EMPTY_ELEMENTDATA则代表一个“真正”的空数组,是利用ArrayList(0)实例化ArrayList时,传递给elementData的数组。其容量capacity就是0。

当initialCapacity < 0时(这是明显不合法的),就通过throw手动抛出 IllegalArgumentException(非法参数异常)。

 

ArrayList(Collection<? extends E> c)

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @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;
    }
}

ArrayList(Collection<? extends E> c)是带参的构造方法,参数c是将要传递给ArrayList的集合。

elementData将首先获得基于集合c转换来的数组(c.toArray())。这里需要注意的是,由于集合c可能为null,所以利用c调用toArray()方法是可能出现 NullPointerException (空指针异常)。

外层的if语句中,由 (size = elementData.length) 语句完成了对ArrayList元素个数size的赋值(其值等于elementData.length,因为c.toArray()得到的数组,数组长度与元素个数是相同的)。

若赋值后的size == 0,那么将会进入else分支,表示这个集合是空的。所以elementData将会获得EMPTY_ELEMENTDATA,即认为此时的ArrayList为空(但不为null)。

若赋值后的size != 0,因为c.toArray()得到的数组长度不可能小于零,所以size != 0就代表size > 0。此时就会进入内层的if语句。内层if语句进行了这样的判断 if(elementData.getClass() != Object[].class)。

之所以需要进行数组的类型判断(保证elementData中得到的数组一定是Object类型的),是因为c.toArray()得到的数组并不一定是Object[]类型的。关于这点,注释中也有提到。

// c.toArray might (incorrectly) not return Object[] (see 6260652)

这里给出6260652这个Bug ID的官方解释链接:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652

这是一个官方的bug,根据官方文档,其描述如下

A DESCRIPTION OF THE PROBLEM :
The Collection documentation claims that

    collection.toArray()

is "identical in function" to

    collection.toArray(new Object[0]);

However, the implementation of Arrays.asList does not follow this: If created with an array of a subtype (e.g. String[]), its toArray() will return an array of the same type (because it use clone()) instead of an Object[].

If one later tries to store non-Strings (or whatever) in that array, an ArrayStoreException is thrown.

Either the Arrays.asList() implementation (which may return an array of component type not Object) or the Collection toArray documentation (which does not allow argumentless toArray() to return arrays of subtypes of Object) is wrong.

这段描述的大概意思是说,collection.toArray()返回的数组理论上应该是Object[]的,但如果集合collection这个集合是使用Arrays.asList得到的,那么其返回值就不一定是Object[]类型的,而是它本身的类型(例如使用Arrays.asList(String[])得到的list调用toArray()方法,就会得到一个String类型的数组),因为asList是使用clone实现的。那么这时如果想向这个数组中存储非String对象,就可能出现 ArrayStoreException (数组存储异常)。

解决这个问题的方式就是进行一次类型判断。如果elementData的类型不为Object[],那么就利用如下语句

elementData = Arrays.copyOf(elementData, size, Object[].class);

将elementData这个非Object[]数组的元素重新拷贝到一个Object[]数组中,再回传给elementData。

这就保证了不会因为Arrays.asList()的使用而出现异常。

 

ArrayList常用方法源码

受限于篇幅,关于ArrayList常用方法源码的分析将在另一篇博文中进行阐述。届时将把链接放在此处。

 

以上分析均是笔者自己结合API和源码的一些认识,可能不甚完备。如有差池,欢迎大家指正。