开篇
ArrayList 是 Java 里面常用的一种集合,它的本质就是一个动态数组, 不仅满足了数组有序存储的特性,还弥补了数组无法动态扩容的缺陷。
ArrayList 的实现原理基于它内部维护了一个 Object 数组,当 ArrayList 容器中的数量达到一定限度时,会重新 new 一个更大的数组,并将原先数组中的数据拷贝过去。
源码分析(基于 jdk1.8.0_191 版本)
ArrayList 中比较常用的方法就是添加元素 add() 和 获取元素 get(),其中add() 方法比较复杂。所以分析的重心就放在了这个方法上,下面就开始一步一步解剖。
几个重要属性
- DEFAULT_CAPACITY 默认容量
- elementData ArrayList 内部维护的数组
- EMPTY_ELEMENTDATA 一个空数组,被所有的空实例共享
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA 也是一个空数组, 与上面的区分开来。以便确定当添加了第一个成员之后扩充多大
- size ArrayList 存放元素的个数
构造方法
ArrayList 里面一共有三个构造方法, 一个无参构造 和 两个有参构造,重点介绍两个关键的构造方法。
-
指定容量的有参构造
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); } }
-
无参构造
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
add() 方法
public boolean add(E e) {
// 1⃣️ 确认容器中的容量, 扩容也就是在这一步进行的
ensureCapacityInternal(size + 1); // Increments modCount!!
// 下面的代码就没什么好说的了,把元素放到指定索引上
elementData[size++] = e;
return true;
}
ensureCapacityInternal() 方法
private void ensureCapacityInternal(int minCapacity) {
// 2⃣️ 下面分析 calculateCapacity
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果通过 ArrayList 创建的实例, 那么在添加第一个元素时, 会得到一个容量为 10 的 ArrayList 实例
// 第二次添加元素时,就不会走这一步了。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY = 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果通过 ArrayList(initCapacity), 指定了初始容量,哪怕是 0 就直接返回。
return minCapacity;
}
// ③ 下面会拿一个空的容器来解释 也就是 通过new ArrayList()创建容器实例
private void ensureExplicitCapacity(int minCapacity) {
modCount++;// 这个变量可以先不管, 他表示 容器中 size 变化的次数。
// 当添加第 1 个元素时, elementData.length的长度 为 0, minCapacity 为10,所以最终会创建一个容量为 10 的 ArrayList
// 当添加第 2 个元素时, minCapacity为 1 ,elementData.length 为10, 无需扩容。
// ......
// 当添加第 11 个元素时,minCapacity为 11, elementData.length 为10, 会扩容1.5倍
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// ④ 具体的扩容算法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 下面的暂不考虑
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 拷贝数组,并添加新元素
elementData = Arrays.copyOf(elementData, newCapacity);
}
总结
1. 当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
2. 第 2 次添加元素,集合不为空,而且由于集合的长度 size+1 是小于数组的长度 10,所以直接添加元素到数组的第二个位置,不用扩容。
3. 第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),
然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。