1.简介
ArrayList是java集合当中的一个重要的类,它继承了AbstractList类,同时实现了List接口,是一个长度可变的集合。其实现了RandomAccess,Serializable,Cloneable接口,所ArrayList 是支持快速访问、序列化、复制的,注意,由于ArrayList的所有方法都没有加关键字synchronized,因此ArrayList是线程不安全的。
其继承体系结构图如下(图来自于idea)
2.ArrayList的底层部分字段概念
我们将解析ArrayList的构造方法和扩容机制,在看构造方法之前,我们先来明确一下ArrayList源码中的一些概念。这些变量和对象大家可能有疑惑,先记住就好了,后面会看到它们的用途,这些看懂了对理解源码有很大的帮助。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8683452581122892189L;
//这是一个序列号版本uid,不用管它
private static final int DEFAULT_CAPACITY = 10;
//不指定大小时创建ArrayList时初始的大小,注意当我们直接创造ArryList时,默然是一个大小为0的空
//数组,只有当我们向其添加元素时,才会触发扩容机制,将这个大小赋给ArrayList。
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
//定义的一个空数组,大小固定为0,不可变。不可变的原因是final关键字。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
//定义的默认空容量的数组,依然不可变。
transient Object[] elementData;
//这里才是真正的存储元素的数组,其类型是object,这就是为什么ArrayList能够放任何类型数据的根
//本原因
private int size;
//数组当中元素的个数,默认为0。默认为0是因为int如果不赋初值则默认为0.
private static final int MAX_ARRAY_SIZE = 2147483639;
//数组能存储的最大元素个数
}
ArrayList的构造方法
ArrayList有三种构造方法,分别是无参、有参和通过传入Collection元素列表进行生成。这里主要讲解其扩容机制,所以选择无参构造演示。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//this.elementData:前面提到的真正存放元素的object数组
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认空数组
}
可以看出来当我们创建一个ArrayList时,其elementData这个真正存放元素的数组被赋予了默认空容量的数组。
ArrayList的扩容机制
ArrayList的扩容机制是比较简单的,我们这里以一次无参构造作为示例,通过它的一次add来分析。
由上文知道,我们创建出来的数组是一个空容量的数组,因此当我们向其添加一个元素时,必然会触发其扩容机制,我们通过以下简单代码进行测试。
public class ArrayList_ {
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
//通过无参创建一个默认初始容量为0的空数组
arrayList.add("测试");
//向这个空数组添加一个元素
}
当执行add时,毫无疑问调用add这个函数,这里的modCount是其父类AbstractList<E>当中的一个属性,用来记录对数组修改的次数,不重要。
public boolean add(E e) {
++this.modCount;
//记录我们对这个数组的修改次数,不重要
this.add(e, this.elementData, this.size);
//真正执行添加的函数,其中e为我们传经来的数据,this.elementData是无参构造时得到的初始容
//量为空的object数组
return true;
}
在add这个函数内部又调用了一个add,注意这两个add函数不是一个函数,真正执行添加的是this.add(E e, Object[] elementData, int s)这个函数。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) { //判断元素个数是否等于当前数组容量。
elementData = this.grow();
}
elementData[s] = e;
this.size = s + 1;
}
首先,它判断了元素个数是否等于当前数组的容量,也就是判断当前数组是不是满的,如果当前空间是满的,就需要扩容了,grow函数就是扩容函数了,扩容后再将被加的元素加到数组中。当然在这里肯定是相等的,那么必然要进入grow函数了,今天的重头戏来了
private Object[] grow() {
return this.grow(this.size + 1);
}
可以看到在里面它又调用了一个grow函数,真正实现的扩容函数,依然是现在马上要执行的grow函数,看看它具体长什么样,
private Object[] grow(int minCapacity) { //当前需要的最小容量
return this.elementData = Arrays.copyOf(this.elementData,
this.newCapacity(minCapacity));
//在这个grow函数当中,进行扩容机制的是this.newCapacity(minCapacity)
}
在这个grow函数当中它执行Arrays.copyOf(this.elementData, this.newCapacity(minCapacity))这个函数(不懂这个函数的可以去查查哦,这里就不细讲),在将newCapacity(minCapacity)执行后的新的数组赋给elementData,现在重头戏来了,我们来研究这个真正执行扩容的newCapacit(minCapacity)函数
private int newCapacity(int minCapacity) { //需要的最小容量
int oldCapacity = this.elementData.length;
//定义一个oldCapacity存储现在的elementData数组元素的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);
//定义一个newCapacity存储扩容后的新数组长度,将原先的数组长度进行右移,然后再加上原先的
//数组长度,相当于加上原先的一半。
if (newCapacity - minCapacity <= 0) { //判断当前新的数组长度是否满足需要的最小容量
if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//重点:如果当前的数组为空数组,那么就执行以下语句,取Math.max(10,minCapacity)当中大的
//一个返回
return Math.max(10, minCapacity);
} else if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity;
}
} else {
return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
}
}
由于当前我们的elementData数组是一个空数组,那oldCapacity=0,newCapacity=0+0*0.5=0,必然会进入当前语句
if (newCapacity - minCapacity <= 0) {
if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(10, minCapacity);
} else if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity;
}
}
同时由于elementData数组是一个空数组,那么返回的一个较大的数
private Object[] grow(int minCapacity) {
return this.elementData = Arrays.copyOf(this.elementData,
this.newCapacity(minCapacity));
//这里的this.newCapacity(minCapacity))就得到一个10,接着执行Arrays.copyof()函数,进行
//对elementData扩容/
}
然后elementData就从原先的0大小,变为了10的大小,扩容完成。逐步返回原先的add函数
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) {
elementData = this.grow();
}
elementData[s] = e;
//if语句执行完成,现在我们的elementData有10个空间大小,将第一个即下标为0的数组指向e
this.size = s + 1;
//数组大小变为1
}
进行赋值,然后当前元素就加入了ArrayList集合,分析完成。
总结
ArrayList的扩容机制是比较简单的,能理解其无参构造下的扩容机制,对其各种情况下的扩容机制应该也不会有太大的难度,希望能对初学者有一定的帮助。