1. 深入理解ArrayList源码
1. ArrayList简介
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。
ArrayList 继承了 AbstractList ,并实现了 List 接口。
主要特点
动态扩容:ArrayList的容量可以自动增长,不需要预先指定固定大小
随机访问:由于基于数组实现,可以通过索引快速访问元素(时间复杂度O(1))
非同步:ArrayList不是线程安全的
允许null元素:可以存储null值
有序集合:保持元素的插入顺序
基础用法:
// 创建ArrayList
ArrayList<String> list = new ArrayList<>();// 添加元素
list.add("Java");
list.add("Python");
list.add("C++");// 访问元素
String first = list.get(0); // 获取第一个元素// 修改元素
list.set(1, "JavaScript"); // 修改第二个元素// 删除元素
list.remove(2); // 删除第三个元素// 遍历
for (String language : list) {System.out.println(language);
}
与数组的比较
大小灵活性:数组大小固定,ArrayList大小可变
功能丰富性:ArrayList提供了更多内置方法(add, remove等)
性能:数组在内存使用和访问速度上略优
2. ArrayList源码分析
下面的一切都是基于jdk1.8 进行分析
2.1 ArrayList的成员变量和构造函数
了解成员变量和构造函数是分析源码的第一步
成员变量:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {// 这些都是成员变量/*** 作用:序列化版本控制标识符* 说明:用于确保序列化和反序列化过程中类的版本兼容性*/private static final long serialVersionUID = 8683452581122892189L;/*** 作用:默认初始容量* 说明:当使用无参构造函数创建ArrayList且第一次添加元素时,默认创建的数组大小*/private static final int DEFAULT_CAPACITY = 10;/*** 作用:空数组共享实例* 说明:用于构造具有指定初始容量为0的ArrayList时使用*/private static final Object[] EMPTY_ELEMENTDATA = new Object[0];/*** 作用:默认容量空数组共享实例* 说明:用于无参构造函数创建的 ArrayList,与 EMPTY_ELEMENTDATA 区分是为了知道第一次添加元素时需要扩容多少*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];/*** 作用:实际存储元素的数组缓冲区* 说明:* transient修饰表示序列化时不会自动序列化此字段* ArrayList通过自定义的 writeObject 和 readObject 方法来控制序列化过程* 实际类型是 Object[],但通过泛型在运行时进行类型检查*/transient Object[] elementData;/*** 作用:ArrayList中实际元素的数量* 说明:* 不等于elementData数组的长度(容量)* 表示列表中实际包含的元素个数* size <= elementData.length*/private int size;/*** 作用:数组最大容量限制* 说明:* 值为2147483639 (Integer.MAX_VALUE - 8)* 减 8 是因为某些虚拟机在数组中保留 header words* 尝试分配更大的数组可能导致 OutOfMemoryError*/private static final int MAX_ARRAY_SIZE = 2147483639;
}
构造函数:
// 带初始容量的构造参数public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else {if (initialCapacity != 0) {throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);}this.elementData = EMPTY_ELEMENTDATA;}}// 无参构造函数public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}// Collection 是集合的父接口// 将collection对象转换成数组,然后将数组的地址的赋给elementDatapublic ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {elementData = a;} else {elementData = Arrays.copyOf(a, size, Object[].class);}} else {elementData = EMPTY_ELEMENTDATA;}}
2.2 ArrayList的添加扩容(第一次添加)
基本扩容流程
初始容量:默认初始容量为10(在第一次添加元素时真正分配)
扩容触发条件:当尝试添加元素时(add操作),发现当前元素数量等于数组容量
扩容大小:新容量 = 旧容量 + (旧容量 >> 1)(即大约1.5倍)
关键源码分析
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;return true;
}private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}
接下来我们一步一步分析,先看代码:
先创建 ArrayList 并添加数据:
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
初始化空数组时会调用无参构造,创建一个空的 ArrayList
// 无参构造函数
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当我们使用 add 方法添加数据时,方法内部就是执行添加、扩容操作。
具体流程如下:
- add 方法(添加元素):
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;return true;
}
先确保内部容量足够所以调用 ensureCapacityInternal 方法验证,
size 是 实际元素数量。
- ensureCapacityInternal 方法(确保内部容量):
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
在这个方法内部还有一个方法 calculateCapacity(elementData, minCapacity),
calculateCapacity 方法是用来计算容量的
参数:
elementData :实际存储元素的数组缓冲区。
这个 elementData 其实就是成员变量当中的 transient Object[] elementData;
它是通过无参构造附的值 实际上 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- calculateCapacity 方法(计算容量):
private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}// 不同版本可能略有差异,有的是用 if 语句判断
private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA :默认容量空数组共享实例 。
DEFAULT_CAPACITY :默认初始容量。
通过calculateCapacity 方法(计算容量)拿到返回值,
由于是第一次进行扩容,其实拿到的返回值就是 10。
然后回到 ensureCapacityInternal 方法(确保内部容量),此时的方法应为:
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(10); }
然后继续调用 ensureExplicitCapacity 方法()。
- ensureExplicitCapacity 方法(确保显式容量):
private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}
这个方法里面设计一个判断,如果所需最小容量大于当前数组长度(相减大于 0),则扩容
扩容方法就是 grow 方法。
- grow 方法(扩容):
// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}
这个方法中,主要进行以下操作:
- 第一行代码先去拿到了数组的容量,然后赋值给 oldCapacity(旧容量)。
- 第二行就是扩容操作,>>1 这是位运算,向右移动 1 位,也就是 除以 2 ,再加上原来的长度(容量),所以 新容量=旧容量*1.5 。
- 最后一行也就是数组拷贝,复制数据到新数组,新数组的长度也就是 newCapacity(新容量)。
- 新数组与原数组地址不同,但数组内储存的数据是相同的
数组拷贝完成之后,就会回到最开始的 add 方法(添加元素),继续执行
继续执行add 方法(添加元素):
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;return true;
}
执行 第二行 elementData[size++] = e;
为 数组的 size 下标处进行赋值,值为 e,也就是咱们最开始传进来的 “1”。
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组 list.add("1"); // 第一次添加,扩容到10
赋完值后,执行 size++,
注意:
// 复制数据到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
Arrays.copyOf()
会创建新数组,所以elementData
不再指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。元素是引用复制(新数组和旧数组的元素指向相同的对象),但 数组对象本身是新创建的(地址不同)。
扩容不会影响原有元素的对象,只是重新分配了一个更大的数组并复制引用。
以上就是第一次扩容的全过程。
2.3 ArrayList的添加扩容(第2 - 10 次添加)
第 2——10 次添加,是不会走扩容操作的,因为在第一次扩容后,数组长度就是 10,容量一直是足够。
我们还是继续分析一波:
我们继续进行第2——10次添加:
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {list.add(i);
}
第2——9次肯定是没有问题的,咱们主要说第 10 次添加。
第 10 次添加:
- add 方法:
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;return true;
}
当进行第 10 次添加,此时 size 的值应该为 9,因为添加了九次。
size + 1 = 10 ,所以 10 继续进入方法 ensureCapacityInternal 执行。
- ensureCapacityInternal 方法,及其内部方法:
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}
进入 ensureCapacityInternal 方法中,继续调用 calculateCapacity 方法。
此时传入 calculateCapacity 的参数中 minCapacity = 10 。
calculateCapacity 中:
由于经过了第一次扩容,条件elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA不在成立,
所以直接返回minCapacity,也是 10。
然后继续 ensureExplicitCapacity(10) :
- minCapacity = 10
- elementData.length = 10(当前数组长度)
- minCapacity - elementData.length > 0,条件不成立,所以不会进行扩容。
所以,第2 - 10 次添加都不会进行扩容。
2.4 ArrayList的添加扩容(第 11 次添加)
第 11 次添加,这个超出数组长度肯定要进行扩容。
我们还是一步一步分析~~~
我们继续进行第 11 次添加:
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {list.add(i);
}
list.add(11);
第 11 次添加:
- add 方法:
// 添加元素方法
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保容量足够elementData[size++] = e;return true;
}
- 此时,size = 10 ,size + 1 = 11
- 11 代入 ensureCapacityInternal 方法。
- ensureCapacityInternal 方法,及其内部方法:
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {return elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(DEFAULT_CAPACITY, minCapacity) : minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果所需最小容量大于当前数组长度,则扩容if (minCapacity - elementData.length > 0)grow(minCapacity);
}
先执行 calculateCapacity 方法:
- minCapacity = 11
- 由于经过了数组拷贝,条件elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA不在成立
- 直接返回 minCapacity
再执行 ensureExplicitCapacity 方法:
- minCapacity = 11
- elementData.length = 10
- 所以 minCapacity - elementData.length > 0 ,条件成立
- 执行 grow 方法进行扩容
- grow 方法(扩容):
// 核心扩容方法
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5if (newCapacity - minCapacity < 0) // 特殊情况处理newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量newCapacity = hugeCapacity(minCapacity);// 复制数据到新数组elementData = Arrays.copyOf(elementData, newCapacity);
}
- minCapacity = 11
- 第一行 oldCapacity = 10 (原来数组长度)
- 第二行 newCapacity = 10 + 5 = 15 (新数组长度)
- 进行 Arrays.copyOf 拷贝,新数组和原来数组的地址是不一致的。
拷贝完成之后,继续回到 add 方法为数组的第 11 号位进行赋值。
这就是后续扩容的步骤。
3. 问题总结
3.1 ArrayList的底层原理实现?
- ArrayList底层是用动态的数组实现的
- ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10
- ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组
- ArrayList在添加数据的时候进行的步骤:
- 确保数组已使用长度(size)加1之后足够存下下一个数据
- 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
- 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
- 返回添加成功布尔值。
3.2 如果在创建ArrayList时声明数组长度,list集合会扩容几次?
eg:ArrayList list=new ArrayList(10)
指定初始大小为 10,会进行下面的构造函数进行创建:
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);} }
关键逻辑解析:
initialCapacity > 0
直接创建一个大小为
initialCapacity
的Object[]
数组,赋值给elementData
。示例:
new ArrayList(10)
→elementData = new Object[10]
(但此时size=0
,因为还没添加元素)。
initialCapacity == 0
使用
EMPTY_ELEMENTDATA
(共享的空数组),避免不必要的内存分配。示例:
new ArrayList(0)
→elementData = EMPTY_ELEMENTDATA
。
initialCapacity < 0
抛出
IllegalArgumentException
异常。示例:
new ArrayList(-1)
→ 直接报错。
很显然“直接创建一个大小为
initialCapacity
的Object[]
数组,赋值给elementData
”,所以并不会进行扩容,因此答案是 0 次。