Java 集合(3)之 ArrayList 源码解析

224 阅读6分钟

ArrayList 在日常开发中非常常用,它是 List 接口的可变长数组的实现,提供了添加、修改、删除、遍历等功能。本文通过源码来分析一下 ArrayList 的实现原理,注意事项,使用场景等(JDK 版本为 1.8)。

ArrayList 的特点如下:

  • ArrayList 基于数组方式实现,可以自动扩容;但由于扩容比较耗时,所以在初始化 ArrayList 时最好预判一下初始化容量;
  • ArrayList 中允许插入 null 元素;
  • 由于实现了 Serializable 接口,重写了 writeObjectreadObject 方法,所以 ArrayList 支持序列化和反序列化;
  • ArrayList 不是同步的;
  • ArrayListiteratorlistIterator方法返回的迭代器是 fail-fast 的。

定义

首先,来看一下 ArrayList 的定义:

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

可以看到 ArrayList 继承或实现了以下类或接口:

  • AbstractList :继承了 AbstractListAbstractList 提供了 List 接口的骨干实现,以最大限度地减少实现 List 所需的工作。
  • List:实现了 List 接口。提供了所有可选列表操作。
  • RandomAccess:表明 ArrayList 支持随机访问。
  • Cloneable:表明其可以被克隆,重写了 clone 方法。
  • java.io.Serializable:表明该类支持序列化。

下面是 ArrayList 的类结构层次图:

属性

ArrayList 的属性主要有:

// 序列化 id
private static final long serialVersionUID = 8683452581122892189L;

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

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

// 如果调用默认构造器(无参构造方法),则返回该空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储当前元素的数组
transient Object[] elementData; 

// ArrayList 的大小(包含的元素数目)
private int size;

// 分配给数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

除此之外,还有一个从 AbstarctList 继承的 modCount 属性,它代表 ArrayList 集合的修改次数。在遍历集合时,如果 modCount 被更改,就会抛出 ConcurrentModificationExceptioin 异常。

构造方法

ArrayList 中,提供了三种构造方法:

默认构造方法

不传入参数的默认构造方法会构造一个初始容量为 10 的空列表。

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

指定容量

这个构造方法会构造一个指定初始化容量为 initialCapacity 的空 ArrayList。如果指定初始化容量为负,则会抛出 IllegalArgumentException 异常。

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

使用给定 collection 构造数组

这个方法会构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 判断 elementData 是否为 Object[] 类型
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 使用空数组代替
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

这里首先会将传入的 elementData 转换为数组,然后使用 Arrays.copyOf 方法将元素拷贝到 elementData 数组中。

基本方法

ArrayList 的主要方法如下:

get(int index) 方法

get 方法用于返回 list 中索引为 index 的元素。

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

方法中首先检查索引,如果索引超过数组的长度,则会抛出 IndexOutOfBoundsException 异常。

然后使用 elementData 方法获取元素:

E elementData(int index) {
    return (E) elementData[index];
}

由于 ArrayList 底层由数组实现,通过数组下标获取元素,它的时间复杂度为 O(1)

add(E e) 方法

add 方法用于在 list 的末尾添加指定的元素 e

public boolean add(E e) {
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}

可以看到,首先是 ensureCapacityInternal(size + 1) 方法,这个方法对数组容量进行检查,如果不够则进行扩容,只供内部使用。

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
    
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

扩容方法 grow,会保证至少能存储 minCapacity 个元素。实现如下:

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

可以看到,扩容后的容量按照如下方式计算:

  • 第一次扩容,使用 newCapacity = oldCapacity + (oldCapacity >> 1); 公式,将容量增加一半;
  • 如果容量还是小于 minCapacity,就将容量扩充为 minCapacity
  • 如果容量大于 MAX_ARRAY_SIZE,就将容量扩充为 Integer.MAX_VALUE

最后,使用 Arrays.copyOf 方法将元素拷贝到新数组中即可。

add(index, element) 方法

这个 add 方法用于在 list 中指定的位置插入元素。

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

这个方法首先会对指定的位置 index 进行检查,如果越界,就会抛出 IndexOutOfBoundsException 异常。然后使用 ensureCapacityInternal 方法判断容量并进行扩容。

然后,使用 System.arraycopy() 方法将索引为 index 位置之后的元素向后移动一位,再将 index 位置赋值为 element

remove(index) 方法

remove 方法用于删除 list 中指定索引 index 位置的元素。

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

首先使用 rangeCheck 方法检查 index 是否越界,然后修改 modCount,表示修改次数加一。再使用 elementData 方法获取 index 位置的元素,方便稍后返回。

使用 size-index-1 计算左移的位数,再使用 System.arraycopy() 方法向左移动一位,也就表示删除了该元素。最后将 --size 位置的元素置为 null,避免对象游离,使 JVM 回收。

Iterator 迭代器

迭代器提供了一种方法来访问集合中的元素。Array 类中的 iterator() 方法用来从容器对象中返回一个指向 list 开始处的迭代器。

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

这里的 Itr 类实现如下:

private class Itr implements Iterator<E> {
    int cursor;       // 下一个要返回的元素的索引
    int lastRet = -1; // 上一个被返回的元素的索引,如果没有返回 -1
    int expectedModCount = modCount;

    Itr() {}

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        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();
    }
    ···
}

Itr 类的主要方法如下:

  • next():获取序列中的下个元素;
  • hasNext():检查序列中是否还有下一个元素;
  • remove():将迭代器最近返回的一个元素删除。