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的成员变量和构造函数
了解成员变量和构造函数是分析源码的第一步
成员变量:
java
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;
}
构造函数:
java
// 带初始容量的构造参数
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对象转换成数组,然后将数组的地址的赋给elementData
public 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倍)
关键源码分析
java
// 添加元素方法
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.5
if (newCapacity - minCapacity < 0) // 特殊情况处理
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 处理超大容量
newCapacity = hugeCapacity(minCapacity);
// 复制数据到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
接下来我们一步一步分析,先看代码:
先创建 ArrayList 并添加数据:
java
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
初始化空数组时会调用无参构造,创建一个空的 ArrayList
java
// 无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当我们使用 add 方法添加数据时,方法内部就是执行添加、扩容操作。
具体流程如下:
- add 方法(添加元素):
java
// 添加元素方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
先确保内部容量足够所以调用 ensureCapacityInternal 方法验证,
size 是 实际元素数量。
- ensureCapacityInternal 方法(确保内部容量):
java
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
在这个方法内部还有一个方法 calculateCapacity(elementData, minCapacity),
calculateCapacity 方法是用来计算容量的
参数:
elementData :实际存储元素的数组缓冲区。
这个 elementData 其实就是成员变量当中的 transient Object[] elementData;
它是通过无参构造附的值 实际上 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- calculateCapacity 方法(计算容量):
java
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 方法(确保内部容量),**此时的方法应为:
javaprivate void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(10); }
然后继续调用 ensureExplicitCapacity 方法()。
- ensureExplicitCapacity 方法(确保显式容量):
java
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果所需最小容量大于当前数组长度,则扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这个方法里面设计一个判断,如果所需最小容量大于当前数组长度(相减大于 0),则扩容
扩容方法就是 grow 方法。
- grow 方法(扩容):
java
// 核心扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5
if (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 方法(添加元素):
java
// 添加元素方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
执行 第二行 elementData[size++] = e;
为 数组的 size 下标处进行赋值,值为 e,也就是咱们最开始传进来的 "1"。
javaArrayList<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次添加:
java
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {
list.add(i);
}
第2------9次肯定是没有问题的,咱们主要说第 10 次添加。
第 10 次添加:
- add 方法:
java
// 添加元素方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
当进行第 10 次添加,此时 size 的值应该为 9,因为添加了九次。
size + 1 = 10 ,所以 10 继续进入方法 ensureCapacityInternal执行。
- ensureCapacityInternal 方法,及其内部方法:
java
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 次添加:
java
ArrayList<Integer> list = new ArrayList<>(); // 初始空数组
list.add(1); // 第一次添加,扩容到10
for(int i = 2; i < 10; i++) {
list.add(i);
}
list.add(11);
第 11 次添加:
- add 方法:
java
// 添加元素方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
- 此时,size = 10 ,size + 1 = 11
- 11 代入 ensureCapacityInternal 方法。
- ensureCapacityInternal 方法,及其内部方法:
java
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 方法(扩容):
java
// 核心扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量=旧容量*1.5
if (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,会进行下面的构造函数进行创建:
javapublic 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 次。