前言
ArrayList
是 Java
集合框架中基于数组实现的动态数组,支持自动扩容和快速随机访问。
以下从数据结构、构造方法和核心方法三个角度分析其源码实现(基于JDK 1.8
)。
源码分析
数据结构
1 | public class ArrayList<E> extends AbstractList<E> |
关键变量说明:
elementData
:实际存储元素的数组,使用transient
修饰,序列化时仅写入有效元素。size
:记录当前元素数量(不是数组长度)。modCount
:记录结构修改次数(如添加、删除),用于迭代器的快速失败机制。
构造方法
ArrayList
一共有三种构造方法,无参构造、指定初始容量构造、基于集合构造。
1 | // 无参构造 |
无参构造:
- 初始化为
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空数组。 - 首次添加元素时扩容到默认容量10(延迟分配)。
指定初始容量构造:
- 直接初始化数组大小为
initialCapacity
,避免后续扩容开销。
基于集合构造:
- 将集合转换为数组,并处理可能的类型不匹配问题(如
Arrays.ArrayList
返回的数组类型)。
核心方法
在 ArrayList
中最为核心的是插入、删除、获取、扩容、缩容这几个方法。
插入(add)
插入一共有两种实现方式,一种是尾部插入,另一种是根据指定位置插入。
1 | // 尾部插入 |
如果使用尾部插入,需要在插入之前调用 ensureCapacityInternal
方法做容量检测,如果容量足够就直接在尾部插入元素,容量不够则扩容后插入。
如果使用指定位置插入,则需要将 index
之后的所有元素后移一位,再在 index
插入元素,在检测容量的基础上,还需要调用 rangeCheckForAdd
方法进行插入范围的检测。
由于 ArrayList
使用动态数组存储元素,插入的平均时间复杂度是 $O(n)$。
删除(remove)
删除一共有两种实现方式,一种是按索引删除,一种是按对象删除。
1 | // 按索引删除 |
按索引删除:
rangeCheck(index)
:检查索引是否合法(0 <= index < size
),防止越界。modCount++
:用于迭代器检测并发修改。- 使用
System.arraycopy()
高效移动元素。 - 将最后一个位置置为
null
,帮助垃圾回收。
按对象删除:
- 分开处理
null
和非null
情况,因为null
不能调用equals()
。 - 使用线性搜索查找元素位置。
- 找到后调用
fastRemove()
执行删除(这是一个私有方法,通常直接操作数组)。 - 返回
boolean
表示是否成功删除。
按对象删除中调用的 fastRemove(int index)
方法实现如下:
1 | /* |
fastRemove(int index)
是一个高效的内部删除方法,通过跳过不必要的检查和返回值处理,优化了删除操作的性能。它与 remove(int index)
的主要区别在于使用场景和开销,是 ArrayList
实现中性能优化的一个典型示例。
由于 ArrayList
使用动态数组存储元素,删除的平均时间复杂度是 $O(n)$。
获取(get)
获取元素的源码实现很简单,只需要检查索引是否在合法范围中,之后直接通过索引从数组中返回元素。
1 | public E get(int index) { |
由于 ArrayList
使用动态数组存储元素,获取的时间复杂度是 $O(1)$。
扩容(grow)
扩容是 ArrayList 的核心方法,当插入的时候容量不足,便会触发扩容,扩容是动态的,主要涉及三个方法:ensureCapacityInternal
、ensureExplicitCapacity
和 grow
。
ensureCapacityInternal(int minCapacity)
:
1 | private void ensureCapacityInternal(int minCapacity) { |
这个方法是确保 ArrayList
内部容量足够的第一步。它接收一个参数 minCapacity
,表示需要的最小容量。
解析:
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:elementData
是ArrayList
底层用来存储元素的数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是一个特殊的空数组,表示ArrayList
刚创建时使用默认容量(还未分配实际空间)。- 如果当前是默认空数组,说明这是第一次添加元素,需要初始化容量。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity)
:DEFAULT_CAPACITY
通常是10
(JDK
版本可能不同,但常见默认值是10
)。- 如果传入的
minCapacity
小于默认容量,就用默认容量(10
)代替,确保初始容量不会太小。
ensureExplicitCapacity(minCapacity)
:- 将调整后的
minCapacity
传递给下一步方法,进入具体的容量检查和扩容逻辑。
- 将调整后的
ensureExplicitCapacity(int minCapacity)
:
1 | private void ensureExplicitCapacity(int minCapacity) { |
这个方法检查当前数组是否需要扩容,并调用 grow
方法进行实际扩容。
解析:
modCount++
:modCount
是ArrayList
的一个修改计数器,用于跟踪结构修改次数(比如添加、删除元素)。- 在快速失败(fail-fast)机制中,如果迭代时检测到
modCount
变化,会抛出ConcurrentModificationException
。
if (minCapacity - elementData.length > 0)
- 检查当前数组长度
elementData.length
是否小于所需容量minCapacity
。 - 如果小于,说明现有容量不够,需要扩容,调用
grow
方法。
- 检查当前数组长度
- 不满足条件时:
- 如果当前容量已经足够(
minCapacity <= elementData.length
),则无需扩容,直接返回。
- 如果当前容量已经足够(
grow(int minCapacity)
:
1 | private void grow(int minCapacity) { |
这是实际执行扩容的核心方法,计算新容量并将原有数据拷贝到新数组中。
解析:
int oldCapacity = elementData.length
:oldCapacity
记录当前数组的容量。
int newCapacity = oldCapacity + (oldCapacity >> 1)
:oldCapacity >> 1
是位运算,相当于除以 2。新容量 = 旧容量 + 旧容量/2 = 旧容量的1.5倍。
例如:旧容量是10,新容量就是15;旧容量是16,新容量就是24。
if (newCapacity - minCapacity < 0)
:检查1.5倍扩容后的容量是否仍然小于
minCapacity
。如果是,直接用
minCapacity
作为新容量,确保至少满足需求。场景:一次性添加大量元素,1.5倍不足以容纳。
if (newCapacity - MAX_ARRAY_SIZE > 0)
:MAX_ARRAY_SIZE
通常是Integer.MAX_VALUE - 8
(为了留出一些头部空间)。如果新容量超过这个最大限制,调用
hugeCapacity
方法处理超大容量情况。
elementData = Arrays.copyOf(elementData, newCapacity)
:使用
Arrays.copyOf
创建一个新数组,大小为newCapacity
。将旧数组中的元素拷贝到新数组中,完成扩容。
1 | private static int hugeCapacity(int minCapacity) { |
整体流程
- 触发扩容:调用
add
或ensureCapacity
时,传入所需的最小容量minCapacity
。 - 初始检查:
ensureCapacityInternal
处理默认空数组情况,调整minCapacity
。 - 容量判断:
ensureExplicitCapacity
确认是否需要扩容。 - 执行扩容:
grow
计算新容量(默认1.5倍),并将数据拷贝到新数组。
扩容涉及数组拷贝,时间复杂度 $O(n)$。
缩容(trim)
缩容的作用是将 ArrayList
底层数组的容量缩减到当前实际元素个数(size
),以节省内存空间。
1 | public void trimToSize() { |
trimToSize()
方法用于“修剪” ArrayList
的底层数组 elementData
,使其容量等于当前元素个数 size
,从而释放多余的内存空间。通常在确定 ArrayList
大小不再变化时调用,以优化内存使用。
后记
对 ArrayList
的特性总结如下:
- 动态调整大小
- 基于数组实现
- 非线程安全
- 允许重复与
null
- 有序性
- 默认自动扩容为原本的1.5倍
总的来说,ArrayList适合需要频繁随机访问和动态调整大小的场景,但不适合高并发或频繁在中间插入/删除元素的操作。