ArrayList源码解析
ArrayList源码解析
1.概述
ArrayList
是 Java 集合框架中基于动态数组实现的顺序容器,允许放入null
元素,底层通过数组实现,非线性安全。
特性:
- 元素有序且可重复。
- 支持随机访问。
- 动态扩容机制。
每个ArrayList
都有一个容量,表示底层数组的实际大小,容器内存储元素的个数不能多余当前容量。当向容器内添加元素时,如果容量不足,容器会自动进行扩容,每次扩容大约为原来的1.5倍。
继承体系
ArrayList
实现了List<E>
,RandomAccess
, Cloneable
, java.io.Serializable
这些接口。
List<E>
:提供了基础的添加、删除、遍历等操作,并且可以通过下标进行访问。RandomAccess
:提供了随机访问的能力。Cloneable
:表示它具有拷贝能力。Serializable
:表示可以被序列化,也就是可以将对象转换为字节流进行持久化存储或网路传输。
2.源码解析
2.1.属性
1 | /** |
(1)DEFAULT_CAPACITY
默认初始容量,当我们通过new ArrayList()
创建一个ArrayList
实例时,底层并不会立即分配容量为 10 的数组,而是初始化为一个空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
。只有在添加第一个元素时,ArrayList
才会将底层数组的容量扩展为 10。这种延迟初始化的设计可以节省内存开销,避免不必要的资源浪费 。
(2)EMPTY_ELEMENTDATA
空数组,new ArrayList(0)
创建的ArrayList
实例。这种情况下,ArrayList
的底层数组被显示的初始化为空数组,且容量为0。这个与通过无参构造器创建的不同,后者使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。
(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA
也是空数组,但是这个专门用于通过无参构造器new ArrayList()
创建的ArrayList
实例。与EMPTY_ELEMENTDATA
的区别在于,当向ArrayList
中添加第一个元素时,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
会被初始化为默认容量DEFAULT_CAPACITY
(即10),而EMPTY_ELEMENTDATA
则不会触发这种行为。
(4)elementData
真正存放元素的地方,他是一个Object[]
类型的数组,为了优化序列化过程,使用了transient
关键字修饰,这意味着该字段不会被默认的 Java 序列化机制直接序列化。
这样做的原因是,ArrayList
只需要序列化实际存储的元素(即前size个元素),而不是整个底层数组,这样可以减少序列化数据的大小,从而提高性能。
(5)size
集合中元素的个数,而不是elementData
数组的长度。size 的值始终小于或等于elementData.length
,因为 elementData
的容量可能会大于实际存储的元素数量(例如扩容后剩余的空闲空间)。
2.2.构造方法
ArrayList(int initialCapacity)
传入初始容量(initialCapacity),如果大于零就初始化elementData为对应大小的数组,如果为0就是用EMPTY_ELEMENTDATA
空数组看,小于零就抛出异常。
1 | public ArrayList(int initialCapacity) { |
无参构造ArrayList()
无参构造,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空数组,在添加第一个元素时扩容为默认初始化容量DEFAULT_CAPACITY = 10
1 | public ArrayList() { |
ArrayList(Collection<? extends E> c)
传入集合参数并将集合转为数组,根据数组长度设置size,并判断集合是否为null,如果不是null,判断是否是ArrayList类型,如果是就直接赋值给elementData,因为 ArrayList 的底层数组已经是 Object[]
类型,无需额外拷贝;如果集合是null,直接将elementData赋值为EMPTY_ELEMENTDATA空数组。
1 | public ArrayList(Collection<? extends E> c) { |
2.3.新增方法
add(E e)
添加元素到末尾,平均时间复杂度为O(1)。
- 首先调用
ensureCapacityInternal(int minCapacity)
方法检查是否需要扩容 - 如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,则初始化为DEFAULT_CAPACITY
- 通过
grow(int minCapacity)
方法进行扩容,新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1));如果新容量还是比所需最小容量(minCapacity)小,则以需要的容量为准。 - 通过
Arrays.copyOf()
方法创建新容量的数组,并将老数组拷贝到新数组。
1 | public boolean add(E e) { |
add(int index, E element)
添加元素到指定位置,平均时间复杂度为O(n)。
- 调用
rangeCheckForAdd(int index)
方法检查索引是否越界 - 检查是否需要扩容
- 把插入索引位置后的元素都往后挪一位
- 在插入索引位置放置插入的元素
- 大小加1
1 | public void add(int index, E element) { |
System.arraycopy()
是 Java 中高效的复制数组的一个本地方法。它允许将一个数组中的元素从指定位置复制到另一个数组的指定位置。
1 | public static native void arraycopy( |
addAll(Collection<? extends E> c)
将一个集合中的元素添加到当前集合,即求两个集合的并集。
1 | public boolean addAll(Collection<? extends E> c) { |
addAll(int index, Collection<? extends E> c)
将指定集合c中的所有元素插入到当前ArrayList的指定位置index.
- 检查索引的合法性
- 将集合转为数组并获取其长度
- 检查是否需要扩容
- 将现有元素向后移动,为新元素腾出空间
- 将集合中的元素复制到目标位置
- 调整列表大小并返回操作结果
1 | public boolean addAll(int index, Collection<? extends E> c) { |
2.4.其他方法
get(int index)
通过下标获取元素。
首先通过
rangeCheck(index)
方法检查是否越上界,如果越上界抛出IndexOutOfBoundsException
异常,这里无需检查下界,在Java中,数组访问本身会自动检查下界,例如:1
2Object[] array = new Object[10];
array[-1] = "test"; // 这里会抛出 ArrayIndexOutOfBoundsException因此,对于
ArrayList
的实现来说,不需要额外检查index < 0
,因为即使不检查,JVM 也会在访问底层数组时抛出异常 。返回数组index位置的元素。
1 | public E get(int index) { |
remove(int index)
删除指定索引位置的元素。
- 检查是否越界
- 获取指定位置的元素
- 如果删除的不是最后一位,则index之后的元素往前移一位
- 将最后一位置为null,方便GC回收
- 返回删除元素
1 | public E remove(int index) { |
remove(Object o)
删除指定元素值的元素,时间复杂度O(n)。
1 | public boolean remove(Object o) { |