Java学习笔记 -- ArrayList源码分析
1.ArrayList简介
ArrayList
底层是用数组实现的,并且它是动态数组,也就是它的容量是可以自动增长的。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
// 略...
}
-
实现
RandomAccess
接口:所以ArrayList
支持快速随机访问,本质上是通过下标序号随机访问。 -
实现
Serializable
接口:使ArrayList
支持序列化,通过序列化传输。 -
实现
Cloneable
接口:使ArrayList
能够克隆。
1.1.底层关键
ArrayList
底层本质上是一个数组,用该数组来保存数据:
transient Object[] elementData;
transient
:Java关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
1.2.追踪源码
1.2.1.重要属性
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
// 返回值Object类型的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储我们添加的数据,关键
transient Object[] elementData;
// 集合数据长度,因为没有给默认值所以就是0
private int size;
1.2.2.无参构造
1、在main函数实例化集合对象,并添加元素
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 1.首先添加10个元素
for (int i = 1; i <= 10; i++) {
list.add(i);
}
// 2.再次添加5个元素
for (int i = 10; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
}
2、在第一行设断点,进行Debug,进入到该类的无参构造函数:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以看到,在构造函数里给存放数据的数组初始化容量大小为空,因为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
就是空数组。
所以这个时候 list = [ ]
4、第一次走到list.add(i)
进入到源码里,如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
可以看到,add()
内部首先调用ensureCapacityInternal()
方法,将集合数据长度先加一,再传入,也就是说第一次传入的是值是1,因为原先默认为0。该方法主要确定数组容量是否足够,是否需要扩容,并不涉及元素的添加,非常重要。
5、进入到该方法当中:
// minCapacity最小容量,首次为 size + 1 = 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
可以看到,又调用了方法的重载,并且首先调用calculateCapacity()
方法,并且将存放数据的数组和添加数据到当前时刻的大小传入,比如第一次添加数据,minCapatiry
的大小等于1,第二次就等于2,然后调用重载方法,将calculateCapacity()
计算的返回结果传入。
6、首先进入到calculateCapacity()
:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
-
第一次进入到这里边,
elementData
是空的,minCapacity = 1
,而判断语句就是判断数组是否为空,所以第一次进入到该方法时,这里的判断条件是成立的。 -
从默认容量
DEFAULT_CAPACITY
和当前数据大小minCapacity
当中取一个最大值返回。因为第一次进入,DEFAULT_CAPACITY = 10
,minCapacity
是传入的size + 1 = 1
。
还要一点需要注意的是:如果这里的
minCapacity
只在集合添加时使用,那直接返回DEFAULT_CAPACITY
即可,因为首次添加数据时minCapacity
为1,而DEFAULT_CAPACITY
为10,一定大于minCapacity
,而下次添加数据数组就不为空所以往后该判断均不成立,所以minCapacity
不可能会比DEFAULT_CAPACITY
大,之所以还要调用方法挑选最大的返回,是因为ArrayList
支持序列化和反序列化,如果直接通过反序列化获得一个集合扔过来,那么minCapacity
的值可能是非常大的,这个时候是需要基于当前大小进行再次扩容。
所以、第一次最终的返回结果就是 10,这个10代表我们需要的数组容量大小,也就是说、首次添加数据发现数组为空,就直接扩容到10的大小。
6、进入重载方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
-
首先是
modCount++
,这是记录集合被修改的次数,因为ArrayList
是非线程安全的。 -
接着判断我们需要的最小容量减去当前数据长度是否大于0,如果条件成立,说明实际需要的容量大小已经大于原始数组容量的大小了,所以就调用
grow()
进行扩容,第一次添加数据,calculateCapacity()
返回的结果是return Math.max(DEFAULT_CAPACITY, minCapacity)
,也就是10,说明我们实际需要的容量为10才够用,但是数组却是空的,所以条件必定成立,然后进行扩容,如下:
7、进入到grow()
方法当中:
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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
如下分析:
- 首先获取数组长度,赋值给
oldCapacity
,也就是0,这是原始容量大小。 - 然后进行位运算,向右移一位,也就是原始数据除以2,然后加上
oldCapacity
,由于是第一次,所以0 + 0 = 0,这是进行位运算后或者是扩容后的新容量大小。 - 这一步非常关键,判断扩容后的容量减去最小容量是否小于0,如果条件成立,就将执行
newCapacity = minCapacity
。也就是执行了newCapacity = 10
,最后将新的容量复制给原始数组,也就是将一个空数组扩容到了大小为 10 的数组。
所以得出结论:初始化ArrayList
数组默认为空,添加第一个数据之前将数组大小扩容到10,往后如果超出10,就会基于10进行1.5倍扩容,这里设计的非常绕,绕来绕去的就是为了扩容到10。
1.2.3.有参构造
如果使用的是有参构造,则初始化容量为指定大小,如下:
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);
}
}
添加数据调用add()
方法,进入calculateCapacity()
方法,如下:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
由于实例化的是有参构造,在里边指定了数组大小,这时判断就不成立了,因为数组不为空,而第一次添加数据minCapacity
依旧为1。
接着进入ensureExplicitCapacity()
:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
下方的条件依旧不成立,第一次添加数据minCapacity
等于1,而数组大小可能为我们指定的8,所以不需要扩容。
所以:如果使用有参构造实例化集合对象,则初始化elementData
为指定大小,如果需要扩容,则直接扩容elementData
的1.5倍。