ArrayList 源码解析
前言
解析 ArrayList 的前提肯定是在我们已经熟悉了 ArrayList 的方法,知晓 ArrayList 数据结构的特性,和使用场景。我们应知道 ArrayList 也只是一个普通的类,我们也可以写出这样的代码,因此不必对源码产生恐惧感。接下来将从两个方面来解析ArrayList源码:Array的基本实现,ArrayList 的方法解析。让我们将源码拷贝下来并将注释翻译,从普通类的角度从上至下进行阅读吧。
ArrayList类的继承和实现
首先我们先来观察ArrayList类的签名这一块,我们可以发现 ArrayList继承了抽闲的 AbstractList 实现了 List, RandomAccess, Cloneable, Serializable。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
继承的
打开AbstractList 我们发现AbstractList 是一个抽象类,它声明了一个最小的 List 应有的基本方法,具体方法如下表所示(不包含迭代器):
方法名 | 是否为抽象 | 重载数 | 是否被覆盖 |
---|---|---|---|
add() | 否 | 2 | 是 |
addALL() | 否 | 0 | 是 |
clear() | 否 | 0 | 是 |
get() | 是 | 0 | 是 |
indexOf() | 否 | 0 | 是 |
remove() | 否 | 0 | 是 |
removeRange() | 否 | 0 | 是 |
set() | 否 | 0 | 是 |
subList() | 否 | 0 | 是 |
listIterator() | 否 | 0 | 是 |
lastIndexOf() | 否 | 0 | 是 |
实现的
AbstractList 实现了RandomAccess标致性接口,表示是支持随机访问的
AbstractList 实现了Cloneable标致性接口,表示是支持可克隆的
AbstractList 实现了Serializable标致性接口,表示是支持可序列化的
Array的基本实现
ArrayList类的属性
打开ArrayList我们可以发现有一些属性,请注意这些属性都是私有的进行修饰的,表示了,这些都是内部的属性不向我们公开,在我们创建数据结构是也要有这样的思维方式。 去掉注释分别是:
根据变量名我们可以得知这是类的序列化版本号,ArrayList是支持序列化的。
private static final long serialVersionUID = 8683452581122892189L;
根据变量名我们可以得知,此属性指定了ArrayList的默认容量
private static final int DEFAULT_CAPACITY = 10;
通过注释可以得知,这个属性是在ArrayList创建以后在其中没有数据时,在方法中共享的一个空的List集合。
private static final Object[] EMPTY_ELEMENTDATA = {};
还是一个空的示例,不过用处和目的与上面的属性不相同,为了解添加第一个元素时要膨胀多少。用处不同,使用名称作一区分,
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这是一个值为空的Obj类型的数组,存储ArrayList元素的数组缓冲区,这个数组的长度决定了ArrayList的容量,非私有以简化嵌套类访问。
transient Object[] elementData;
保存了ArrayList容器内现有元素的个数
private int size;
ArrayList类的构造方法
ArrayList类有 3 个构造方法 1 个无参数的 2 个有参数的,我们来看看他们都做了什么。
无参数构造方法
阅读此段代码我们发现,在我们new一个无参构造方法的时候,其实将一个有着默认容量(数组的默认容量)的元数据为空的一个数组将其值赋给了elementData属性,elementData属性成为了一个空的有着默认容量的数组,此时elementData这个引用也就指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,elementData从一个为null的属性变成了一个没有元素的实质性的数组
/*构造一个初始容量为 10 的空列表。*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
第一个有参数的构造方法
阅读发现次构造方法需要传入一个 int 类型的初始化容量,为ArrayList指定容量。
如果指定了大于0的初始化容量,则创建一个Obj类型的数组赋给缓冲数组,此时缓冲数组才有了值,不为null了,
如果指定了0 为初始化值,此时我们不能创建化一个没有长度的数组所以java给了我们一个元素为空的且有默认值的数组。
其他初始化值皆为不合法抛出异常。
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);
}
}
第二个有参数的构造方法
观察这个构造方法我们发现其参数列表需要一个 Collection 类型的参数,
很明显就是将一个List或者Set转换为一个ArrayList,请看我在代码中添加的注释。
public ArrayList(Collection<? extends E> c) {
//将参数转换为数组,并赋值给elementData 属性使此属性拥有实质。
elementData = c.toArray();
//如果得到值的elementData 也就是传入参数的元素个数不为0的话执行以下代码
if ((size = elementData.length) != 0) {
//如果传入的参数不是一个数组类型的话执行以下代码
if (elementData.getClass() != Object[].class)
//将数据拷贝后存入elementData 目的是我们必须得到一个数组。
elementData = Arrays.copyOf(elementData, size, Object[].class);
//其他情况则直接放弃参数,给一个空的有着默认大小的数组。
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
小小总结
读完上面这些可以的出结论:
不论使用哪种方式创建ArrayList对象。都会将elementData 属性从其值为null设置为一个Obj类型的数组,并且为size属性赋值,这就证明了ArrayList底层是一个Obj类型的数组,我们最终操作的到ArrayList集合是ArrayList类中的elementData 属性,构造方法的执行完成了ArrayList集合的创建与初始化(容量初始化和元素个数初始化)。
ArrayList 的方法解析
以下可能只分析常用的方法,这根据今天所剩的时间而定,其中不包含Array中的私有方法(私有的方法时为了服务ArrayList内部的各种方法我们开发用不到)。可能有些读者会问:为什么要分析这么简单的方法和分析的这么细节呢,这是为了以后节省时间,和更加细致的了解ArrayList的实现思想与原理,也为后来者省力,正是这样的细致 才能使得我们的代码水平越来越高。
add()方法
一个参数的add()方法:
阅读发现返回值是一个boolean类型需要一个E(Element在集合中使用,因为集合中存放的是元素 ),
public boolean add(E e) {
//ensureCapacityInternal是ArrayList中的方法,
ensureCapacityInternal(size + 1); // Increments modCount!!
//将参数添加到数组的末尾处(size是元素的个数,也表示了最后一个元素的位置,使用++操作得到第一个空位将e添加到素组的末尾)
elementData[size++] = e;
//程序执行到此,添加成功返回true
return true;
}
两个参数的add()方法:
两个参数:要插入指定元素的索引,和要添加的元素。
/*在此列表中的指定位置插入指定元素。将当前在该位置的元素(如果有)和任何后续元素向右移动(向它们的索引添加一个)*/
public void add(int index, E element) {
//ArrayList本类中的方法,
rangeCheckForAdd(index);
//因为要添加新元素首先要保证数组的容量要足够
ensureCapacityInternal(size + 1); // Increments modCount!!
/*将指定源数组中的数组从指定位置复制到目标数组的指定位置。将原始的elementData的数据以流的形式考出在重新考入到elementData中,此方法的参数elementData是原始数组,index是源数组中的起始位置
elementData是新的数组,index + 1是目的地数据中的起始位置,size - index是要复制的数组元素的数量。如下图*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将要添加的数组添加到指定的位置
elementData[index] = element;
//将ArrayList的元素个数加1
size++;
}
addAll()方法
返回boolean需要Collection类型的参数
/**按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾*/
public boolean addAll(Collection<? extends E> c) {
//将参数转换为数组并赋给局部变量a
Object[] a = c.toArray();
//获取参数的元素个数将他赋给局部变量numNew
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//拷贝数组
System.arraycopy(a, 0, elementData, size, numNew);
//将ArrayList的元素个数更新
size += numNew;
//如果要添加的元素的个数不为0,那么肯定成功添加了元素返回true
return numNew != 0;
}
clear()方法
/*从此列表中删除所有元素(可选操作)*/
public void clear() {
//modCount是父类AbstractList中的一个受保护的属性,AbstractList默认已经继承,为迭代器提供参考,表示了此列表在结构上被修改的次数。结构修改是那些改变列表大小的修改
modCount++;
/* 让GC做好自己的工作*/
//简单粗暴,直接将集合中的每个元素修改为null
for (int i = 0; i < size; i++)
elementData[i] = null;
//更新数据
size = 0;
}
contains() 方法
返回boolean 类型,需要Object 类型的参数
/*如果此列表包含指定的元素,则返回 true 。*/
public boolean contains(Object o) {
//如果indexOf不>0则证明有元素,有元素就返回元素
return indexOf(o) >= 0;
}
ensureCapacity() 方法
这个方法在AarrayList的添加和插入中很常见
需要一个最小容量参数
/*如果需要,增加此 ArrayList实例的容量,以确保它可以至少保存最小容量的参数所指定的元素数。*/
public void ensureCapacity(int minCapacity) {
//如果ArrayList集合不是初始状态的
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//如果为真则minExpand = 0
? 0
//如果为假则指定一个默认值10
: DEFAULT_CAPACITY;
//如果要扩充的容量大于elementData 的实际容量,则扩展。
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
get()方法
返回一个元素,需要一个下标
public E get(int index) {
//如果>=下标值则超出了集合的容量范围 会抛出异常
rangeCheck(index);
//执行到这里则表示正常,返回下标所指的元素
return elementData(index);
}
set()方法
返回一个元素,需要下标和元素
/*用指定的元素替换此列表中指定位置的元素。*/
public E set(int index, E element) {
//如果>=下标值则超出了集合的容量范围 会抛出异常
rangeCheck(index);
//获取index下标位置旧元素赋值给oldValue局部变量
E oldValue = elementData(index);
//将新元素赋给下标为index的数据
elementData[index] = element;
返回旧值
return oldValue;
}
retainAll() 方法
返回boolean 类型需要一个Collection
/*仅保留此列表中包含在指定集合中的元素。*/
public boolean retainAll(Collection<?> c) {
//判断c是不为 null
Objects.requireNonNull(c);
return batchRemove(c, true);
}