ArrayList 源码分析
1. 成员属性
// 默认的容量
private static final int DEFAULT_CAPACITY = 10;
// 空元素时,对象数组的指向
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认的为空时的指向,用于无参构造方法初始化
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList底层,实际的存储
transient Object[] elementData; // non-private to simplify nested class access
// 实际存储的对象的数量 int类型,默认为0
private int size;
2. 构造方法
a. 无参构造
无参构造方法很简单,仅仅是将对象数组指向了默认的空数组.
b. 有参构造
有参构造即传入初始化的容量,如果初始化的容量大于0,则将数组对象指向一个新的容量为initialCapacity
的对象数组,如果容量等于0,则将对象数组指向空数组EMPTY_ELEMENTDATA
,而如果小于0,则直接抛异常不合法.
c. 有参构造(Collection集合)
传入Collection集合为参数的构造方法也简单,首先将传入的集合toArray()
(返回一个包含所有集合里有的Object数组),在判断对象数组的大小,如果大于0,则判断传入的Collection集合是否是Object类型的数组,不是则转换一遍(这里也能看到一点类型擦除的影子).否则则将对象数组赋值为空对象数组.
3. 添加元素
添加元素为add()
方法:
可以看到,第一步就先判断了容量是否符合
此方法又调用了另外两个方法,
calculateCapacity
用于计算扩容的容量:
首先判断是不是为默认的空对象数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),如果是,则表明我们当前实际存储的对象数组是一个空数组,那么我们就取默认大小和minCapacity的最大值,而DEFAULT_CAPACITY
为10,如果为空数组,理论上此处minCapacity为1,因此会返回10.也就对应了我们的无参构造方法,无参构造方法初始化时,先不会给对象数组分配空间,而是让它指向默认的空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),直到第一次添加元素时,会将它默认的初始化成大小为10的对象数组.
否则就直接返回需要扩容的大小
ensureExplicitCapacity
方法则是判断是否需要扩容的
获取需要扩容的大小后,根据判断minCapacity(存储新值需要的大小)减去实际数组的大小,如果大于0,表示当前数组无法继续容纳了,需要扩容,传入当前需要的容量大小.
真正的扩容逻辑,在grow
方法里面
首先获取了原容量,在计算新容量,新容量为原容量的1.5倍(右移1位等于乘2),如果新容量比当前需要的容量还要小,那么我们的计算值无效,直接将新容量的值改为我们当前需要的容量大小,而如果当前的新容量大于了最大的数组容量(整数的最大值-8),那没办法,我直接给你申请最大整数个容量的数组,再大我就直接给你OOM了.
MAX_ARRAY_SIZE
值为Integer.MAX_VALUE-8
原因与对象数组的大小在某些虚拟机可能存储在对象头上有关,大于这个值则有可能出现OOM.
在判断完需要扩容后,通过Arrays.copyOf
方法将原数组赋值过来
而为什么新容量为原容量的1.5倍,主要为了方便后续使用,空间换取时间,不然每次添加元素就扩容一遍?
最后在通过elementData[size++] = e;
方法直接完成赋值
4. 移除元素
移除元素比较简单,主要就是通过计算索引的边界,然后通过System.arraycopy
native方法直接copy一份新的出来.