ArrayList介绍
ArrayList
特点如下:
- 动态数组 :使用动态数组实现,能够根据需要自动动态扩容。
- 随机访问 :由于
ArrayList
基于数组实现,所以支持通过索引快速访问集合中的元素,这使得它在需要频繁按位置访问元素的情况下非常高效。 - 线程不安全 :默认情况下,
ArrayList
不是同步(线程安全)的。在多线程场景下,如果需要线程安全的列表,可以选择使用Collections.synchronizedList(List l)
方法将 ArrayList 包装成一个线程安全的列表,或者直接使用并发包java.util.concurrent
中的CopyOnWriteArrayList
类。 - 序列化 :
ArrayList
实现了Serializable
接口,支持将对象状态保存到字节流中以便于存储或传输。为了优化性能,ArrayList
使用transient
关键字修饰内部数组elementData
并通过自定义序列化逻辑来仅序列化实际存储的元素而非整个数组。 - 迭代器支持 :
ArrayList
提供了对Iterator
和ListIterator
的支持,允许遍历集合中的元素,同时提供了移除元素的能力(通过ListIterator
)。
ArrayList
的类继承结构如下:

- 继承自
AbstractList
:AbstractList
提供了对List
接口的部分实现,使得ArrayList
不需要重新实现这些基础方法。 - 实现了
List<E>
接口 :List
接口定义了允许重复元素的有序集合(即列表)。ArrayList
实现了这个接口,提供了所有声明的方法,如添加、删除、获取元素等操作。 - 实现了
RandomAccess
接口 :这是一个标记接口,没有包含任何方法。实现RandomAccess
接口表明ArrayList
支持快速随机访问,这意味着通过索引访问元素的操作在ArrayList
中是非常高效的。虽然所有List
实现都支持随机访问,但由于底层结构的差异,它们的访问速度可能不同,因此并非所有List
集合都能提供这种"快速"的随机访问功能。 - 实现了
Cloneable
接口 :表示支持对象的克隆操作。ArrayList
提供了clone()
方法来创建当前列表的一个浅拷贝。 - 实现了
Serializable
接口 :表示ArrayList
支持序列化操作,可用于对象的持久化存储或网络传输。
ArrayList源码分析
以 jdk1.8
版本为例,ArrayList
源码的核心方法剖析如下:
存储结构
java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 最大数组容量
* 为什么要减去8:为了预留空间以应对JVM内部开销和潜在的实现限制(比如每个对象都有一定量的对象头开销、数组可能需要额外的空间来存储数组的长度信息、某些 JVM 可能需要额外的空间用于内部管理等),通过预留一些空间,可以增加代码的稳定性和兼容性,避免由于接近系统或JVM内部限制而导致的问题。这有助于防止潜在的错误或不稳定行为,特别是在不同的运行环境和平台上。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 用于空实例的共享空数组对象。
* 当用户指定容量为0时,返回的是该空数组。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小空实例的共享空数组对象。
* 当调用无参构造方法,返回的是该空数组。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存 ArrayList 数据元素的数组。ArrayList的容量就是该数组的长度。
* 当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,在第一次添加元素进入ArrayList时,数组大小将扩容到DEFAULT_CAPACITY值:10。
* 标记为 transient :在对象被序列化的时候,elementData 数组不会被序列化。ArrayList 类重写了 writeObject 和 readObject 方法,从而实现了自定义的序列化逻辑,只对实际存储的元素进行序列化处理,而不是整个 elementData 数组。这样不仅减少了序列化后的数据量,还提高了序列化和反序列化的性能。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 数组长度:ArrayList 所包含的元素个数
*/
private int size;
}
构造函数
无参构造
java
/**
* 无参构造函数:构造一个初始容量为10的空列表。
* 由前面的属性可知,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空的 Object[] ,也就是说初始化时其实是一个空数组,当使用 add 方法添加第一个元素时,elementData 数组容量才变成 10。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
指定容量构造
java
/**
*
* 构造一个指定容量为 initialCapacity 的空列表。
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果初始容量大于0,则创建指定初始容量大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果初始容量等于0,将空数组赋给elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
指定集合构造
java
/**
* 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
*/
public ArrayList(Collection<? extends E> c) {
// 将指定 collection 转换为数组,直接赋给 elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 如果数组的实际大小值大于 0 、且 elementData 不是 Object 类型数据,则执行 Arrays.copy 方法,把 collection 对象的内容深拷贝到 elementData 中
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果数组的实际大小等于0,将空数组 EMPTY_ELEMENTDATA 赋给 elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
操作方法
add(E e)
java
/**
* 将指定的元素追加到此列表的末尾
*/
public boolean add(E e) {
// 检查数组容量,如果不够,则容量加1,保证加入的对象有空间可以存储
ensureCapacityInternal(size + 1); // Increments modCount!!
// 把元素赋值到数组末尾
elementData[size++] = e;
return true;
}
/**
* 检查数组容量,在容量不够时进行自动扩容
* private表明只供类内部使用
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 根据当前数组元素和给定的最小容量,计算数组所需容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果当前数组元素为空数组(初始情况),返回默认容量(DEFAULT_CAPACITY == 10)和最小容量中的较大值作为所需容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 否则直接返回最小容量
return minCapacity;
}
/**
* 保证数组容量至少为minCapacity,若容量不足则自动扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
// 将修改次数(modCount)自增1
modCount++;
// 若当前数组容量小于minCapacity,则扩充数组容量
if (minCapacity - elementData.length > 0)
// 调用grow方法执行扩容
grow(minCapacity);
}
/**
* 执行数组扩容
*/
private void grow(int minCapacity) {
// oldCapacity为旧容量,即当前数据的实际容量
int oldCapacity = elementData.length;
// 新容量newCapacity计算:
// 使用位运算代替乘除运算,性能更优:oldCapacity右移一位,等同于oldCapacity/2
// 因此,新容量计算结果为旧容量的1.5倍
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:
// 根据计算得到的新容量,执行扩容:
// Arrays.copyOf方法会根据指定的新容量newCapacity创建一个新的数组,并将原数组elementData中的元素复制到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 比较 minCapacity 和 MAX_ARRAY_SIZE,获取最大容量
*/
private static int hugeCapacity(int minCapacity) {
// 如果 minCapacity < 0,抛出异常
if (minCapacity < 0)
throw new OutOfMemoryError();
// 如果想要的容量(最小需求容量)大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE(2147483647),否则分配MAX_ARRAY_SIZE(2147483639)。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
在 ArrayList
中添加元素时,如果当前的容量不足以容纳新添加的元素,就会触发动态扩容机制:
- 检查容量 :当调用
add
方法试图向ArrayList
中添加一个新元素时,首先会检查当前数组elementData
的容量是否足够。如果当前容量足以容纳新元素,则直接将元素添加到列表的末尾。 - 计算新容量 :如果当前容量不足以容纳新元素,则需要进行扩容。默认情况下,新的容量是当前容量的 1.5 倍 。
- 这个计算方式可能会根据不同的JDK版本有所变化,但目的是逐步增加容量以减少频繁扩容带来的性能开销。
- 创建新数组并复制数据 :确定了新的容量后,
ArrayList
会使用Arrays.copyOf
方法,创建一个新的、更大容量的数组,将原数组中的所有元素复制到新数组中。 - 更新引用 :最后,
ArrayList
将内部的elementData
引用指向这个新创建的数组,并将新元素添加到适当的位置。
这种机制保证了 ArrayList
可以随着元素的添加自动增长其容量,而不需要用户手动调整大小。但由于涉及到数组复制操作,每次扩容都会带来一定的性能开销,所以为了优化性能,应尽量预估好初始容量,避免频繁的扩容操作。

add(int index, E element)

java
/**
* 在列表的指定位置插入指定元素
*/
public void add(int index, E element) {
// 对index进行界限检查:若index的位置超出数组的大小或者小于0,则抛异常IndexOutOfBoundsException
rangeCheckForAdd(index);
// 检查数组容量,如果不够,则容量加1,保证加入的对象有空间可以存储。与 add(E element) 流程类似
ensureCapacityInternal(size + 1); // Increments modCount!!
// 使用 System.arraycopy 把需要插入的位置(index)后面的元素都往后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将element插入index位置
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
addAll(int index, Collection c)
java
/**
* 将指定集合中的所有元素插入到列表指定位置
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查index边界:若index大于数组实际大小、或小于0,则抛出IndexOutOfBoundsException异常
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
// 数组扩容:检查数组容量,如果不够,则容量加numNew,保证加入的对象有空间可以存储
ensureCapacityInternal(size + numNew); // Increments modCount
// 需要移动的元素个数
int numMoved = size - index;
if (numMoved > 0)
// 数组从index位置开始的所有元素,往后移numNew个位置,腾出空间
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 在index处插入collection
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove(int index)

java
/**
* 删除列表中指定位置上的元素
*/
public E remove(int index) {
// 检查index是否在范围内,否则报错
rangeCheck(index);
// 修改次数加1
modCount++;
// 将要删除的元素赋值给oldValue
E oldValue = elementData(index);
// 删除其实就是将任何后续元素移动到左侧(从其索引中减去一个元素)。
// numMoved是需要往左移的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 把index后的所有元素(numMoved个)都左移一位,通过拷贝覆盖实现删除
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
// 清理对象引用,给GC回收
elementData[--size] = null; // clear to let GC do its work
// 返回从列表中删除的元素
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
remove(Object o)
java
/**
* 从列表中删除指定元素(如果存在)。
* 如果列表不包含该元素,则无更改。
* 如果列表中包含多个该元素,则只删除第一个出现的。
* 如果此列表包含指定的元素,返回true。
*/
public boolean remove(Object o) {
if (o == null) {
// 删除null元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 删除与指定元素相同的元素(通过equals比较是否相同)
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* 快速移除:私有方法,跳过了边界检查,并且不返回被移除的值。
*/
private void fastRemove(int index) {
modCount++;
// numMoved是需要往左移的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 把index后的所有元素(numMoved个)都左移一位,通过拷贝覆盖实现删除
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
// 清理对象引用,给GC回收
elementData[--size] = null; // clear to let GC do its work
}
clear()
java
/**
* 从列表中删除所有元素
*/
public void clear() {
modCount++;
// clear to let GC do its work
// 把数组中所有的元素的值设为null,清理对象引用后才能被GC回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
trimToSize()
java
/**
* 把数组容量缩小到当前列表的实际大小(size)。
* 可以使用此操作来最小化ArrayList实例的存储。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
// 若size == 0,则使用空数组实例
? EMPTY_ELEMENTDATA
// 若size > 0,则创建一个size大小的数组,把元素拷贝过去
: Arrays.copyOf(elementData, size);
}
}
get(int index)
java
/**
* 返回此列表中指定位置的元素。
*/
public E get(int index) {
// 检查index下标
rangeCheck(index);
// 返回当前下标位置的元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
set(int index, E element)
java
/**
* 把列表中指定位置的元素替换为指定的元素 。
*/
public E set(int index, E element) {
// 对index进行界限检查
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
// 返回原来在这个位置上的元素
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
核心原理总结
ArrayList
通过一个可动态调整大小的数组来实现列表操作,允许快速随机访问元素,但插入和删除操作可能需要移动元素以维护数组的连续性。- 重要成员变量:
transient Object[] elementData;
:使用数组 存储列表元素。- 使用
transient
关键字标记以避免默认序列化整个数组,因为实际使用的可能只是数组的一部分。 - 若不指定数组初始容量,则默认初始容量是
10
。
- 使用
int size;
:表示列表中实际存储的元素数量。
ArrayList
中允许元素为null
。- 增(Add):向列表添加元素。如果当前容量不足以容纳新元素,则会调用
grow()
方法进行动态扩容。 - 动态扩容机制:当添加元素导致容量不足时,
ArrayList
会自动扩容。新的容量计算方式通常是原容量的 1.5 倍 左右。扩容时,会使用Arrays.copyOf()
创建一个新的更大容量的数组,并将所有原有元素复制过去。ensureCapacity(int minCapacity)
:当预先知道需要增加很多元素时,可以手动调用此方法来增加ArrayList
的容量,以减少扩容次数。trimToSize()
:将ArrayList
实例的容量调整为当前列表大小,减少空间浪费。
- 删(Remove):移除指定索引处的元素,实际上就是把该索引之后的所有元素向前移动
x
位来填补空缺,同时减少列表的size
。 - 查/改(Get/Set):直接返回/更新数组中指定索引处的元素,数组结构使得查改操作非常高效。