剖析 ArrayList
剖析 ArrayList
SunArrayList
是 Java
中的一个动态数组实现类,可以根据需要自动调整数组的大小
1. 基本用法
ArrayList 是一个泛型容器,新建 ArrayList 需要实例化泛型参数,比如:
1 | ArrayList<Integer> intList = new ArrayList<Integer>(); |
ArrayList 的主要方法有:
1 | public boolean add(E e) //添加元素到末尾 |
简单示例:
1 | ArrayList<String> strList = new ArrayList<String>(); |
2. 基本原理
ArrayList 内部主要包含:
- 一个
Object
数组elementData
记录存入的元素; - 一个
int
类型的变量size
记录实际存储的元素个数。
1 | /** |
2.1 构造函数
ArrayList 的构造函数主要有三个:
1 | public ArrayList(); // 无参构造函数 |
先看无参构造函数,无参构造函数很简单,将 elementData
数组赋值为常量
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,这是一个空数组
1 | public ArrayList() { |
指定初始容量的构造函数如下,如果初始容量大于 0 就创建一个
Object
类型的数组,容量为 initialCapacity
并赋值给 elementData
;如果初始容量等于 0 就将
elementData
数组赋值为常量
EMPTY_ELEMENTDATA
,这也是一个空数组
1 | public ArrayList(int initialCapacity) { |
传入集合的构造函数如下,用于将一个现有的
Collection
(集合)转换为一个新的 ArrayList
对象
1 | public ArrayList(Collection<? extends E> c) { |
2.2 add
方法
add
方法用于向 ArrayList
末尾添加元素
1 | public boolean add(E e) { |
首先调用 ensureCapacityInternal(int minCapacity)
方法检查容量是否足够,当前只添加一个元素,所以需要的最小容量为
size + 1
1 | private void ensureCapacityInternal(int minCapacity) { |
该方法先调用了 calculateCapacity()
方法计算所需容量
1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
calculateCapacity()
方法中,如果当前
elementData
数组等于
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,即还是个空数组,容量就取
DEFAULT_CAPACITY
和 minCapacity
中较大的一个,DEFAULT_CAPACITY
是一个常量,值为
10
,否则就返回 minCapacity
所以
ArrayList
的初始容量为
0
,添加第一个元素时才会扩容到
10
接着 calculateCapacity()
方法调用
ensureExplicitCapacity()
方法,判断所需最小容量是否大于当前数组的长度,如果是则调用
grow()
方法扩容,否则无需扩容直接返回
1 | private void ensureExplicitCapacity(int minCapacity) { |
modCount
这个变量用来记录集合被修改的次数。每当集合结构发生变化(例如添加、删除元素等),modCount
就会增加 1,在使用迭代器时,迭代器会检查modCount
是否发生变化,如果变化了,就会抛出ConcurrentModificationException
,以防止在遍历集合时,集合被修改,导致不一致的结果。
grow()
函数正式对数组进行扩容
1 | private void grow(int minCapacity) { |
newCapacity = oldCapacity + (oldCapacity >> 1);
即新的容量是原容量的
1.5 倍。
如果新容量小于所需最小容量,则将所需的最小容量赋值给新容量;
如果新容量大于 MAX_ARRAY_SIZE
需要调用
hugeCapacity()
方法,该方法将新容量设置为
Integer
类型的最大值
所以 ArrayList
的容量是有限的,最大为
Int 类型的最大值
1 | private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; |
最后 grow()
方法使用
Arrays.copyOf(elementData, newCapacity)
方法创建一个新的数组,容量为 newCapacity
并赋值给
elementData
,完成扩容。
扩容完成后,add()
方法执行
elementData[size++] = e;
将元素添加到数组末尾并将
size + 1
同时整个过程没有对 null
值做任何处理,所以
ArrayList
是可以添加
null
值的
2.3 remove
方法
remove()
方法用于删除指定下标处的元素并返回删除的元素
1 | public E remove(int index) { |
首先会计算需要移动的元素数量 size - index - 1
,然后调用
System.arraycopy()
方法移动元素。
elementData[--size]=null;
这行代码将 size
减 1,同时将最后一个位置设为 null
,设为 null
后不再引用原来对象,如果原来对象也不再被其他对象引用,就可以被垃圾回收。
2.4 ensureCapacity
方法
ArrayList()
还有一个 ensureCapacity()
方法是提供给用户调用的,用于在向 ArrayList
添加大量元素之前对数组进行扩容,以减少增量重新分配的次数
1 | public void ensureCapacity(int minCapacity) { |
3.
System.arraycopy()
和 Arrays.copyOf()
方法
ArrayList
中大量调用了这两个方法
3.1 System.arraycopy()
方法
System.arraycopy()
是一个用于快速复制数组元素的本地(native)方法,它提供了一种高效的方式来复制一个数组的部分或全部内容到另一个数组中
1 | /** |
3.2 Arrays.copyOf()
方法
Arrays.copyOf()
方法用于创建一个新的类型为
T[]
的数组,长度为 newLength
,并复制原始数组
original
中的元素到新数组。
1 | public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { |
首先检查要创建的新数组类型是否是 Object[]
:
- 如果是
Object[]
,直接创建一个Object
数组 - 如果不是,则使用反射 API 的
Array.newInstance
方法,根据类型创建特定类型的数组
然后使用 System.arraycopy()
方法将原始数组中的数据复制到新数组