ArrayList 在日常开发中非常常用,它是 List 接口的可变长数组的实现,提供了添加、修改、删除、遍历等功能。本文通过源码来分析一下 ArrayList 的实现原理,注意事项,使用场景等(JDK 版本为 1.8)。
ArrayList 的特点如下:
ArrayList基于数组方式实现,可以自动扩容;但由于扩容比较耗时,所以在初始化ArrayList时最好预判一下初始化容量;ArrayList中允许插入null元素;- 由于实现了
Serializable接口,重写了writeObject和readObject方法,所以ArrayList支持序列化和反序列化; ArrayList不是同步的;ArrayList的iterator和listIterator方法返回的迭代器是fail-fast的。
定义
首先,来看一下 ArrayList 的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
可以看到 ArrayList 继承或实现了以下类或接口:
AbstractList:继承了AbstractList。AbstractList提供了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():将迭代器最近返回的一个元素删除。