一、概述
ArrayList类是AbstractList的子类,实现了具体的add(), set(), remove()等方法。它是一个可调整大小的数组可以用来存放各种形式的数据。
二、源码分析
(1) 类的声明
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
ArrayList类继承自AbstractList类,实现了List、RandomAccess、Cloneable、Serializable四个接口。具体作用如下:
* 继承AbstractList类,可以自己实现add(), set(), remove()等方法,还可以用AbstractList类的modCount来实现快速失败。
* 实现List接口,一是为了增加可读性,清晰看到实现的接口,二是降低维护成本,如果AbstractList类不实现List了,ArrayList类也不受影响。
* RandomAccess接口是标记接口,表示实现它的类支持快速随机访问。在使用循环遍历List的时候应该判断下这个集合是否是RandomAccess的实例,如果是就是用for循环来操作,如果不是就是使用iterator迭代器来操作。随机访问一般是通过index下标访问,行为类似数组的访问。而顺序访问类似于链表的访问,通常为迭代器遍历。以List接口及其实例为例。ArrayList是典型的随机访问型,而LinkedList则是顺序访问型。
* Cloneable接口也是克隆标记接口,表示此类可以被克隆,此类的实例可以调用clone()方法;未实现Cloneable接口的类的实例调用clone()方法会报错,在Object类中已经定义。
* Serializable接口是序列化标记接口,表示此类可以被序列化到内存中。目的是为类可持久化,比如在网络传输或本地存储,为系统的分布和异构部署提供先决条件。
(2) 成员变量
//serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。
private static final long serialVersionUID = 8683452581122892189L;
//定义初始容量10
private static final int DEFAULT_CAPACITY = 10;
//定义一个空数组,使用场景为初始容量为10或者构造函数中传入空集合时,下面源码中可看到具体使用场景。
private static final Object[] EMPTY_ELEMENTDATA = {};
//定义一个空数组,使用场景是调用无参构造函数时,下面源码中可看到具体使用场景。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//transient修饰的Object类型的数组,表示此数组不支持序列化。也是ArrayList的内部容器。
transient Object[] elementData; // non-private to simplify nested class access
//数组的实际元素数量
private int size;
//定义一个数组对象最大容量,只是用来做比较,ArrayList能达到的最大容量还是Integer.MAX_VALUE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(3) 构造方法
//无参构造函数,直接将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给内部容器elementData
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定容量的构造函数
public ArrayList(int initialCapacity) {
//如果指定容量大于0,直接赋值一个指定容量的Object数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//若指定容量等于0,则将EMPTY_ELEMENTDATA赋值给内部容器elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果指定容量小于0,则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//指定集合的构造函数
public ArrayList(Collection extends E> c) {
//直接将集合转为数组赋值给内部容器elementData
elementData = c.toArray();
//先将elementData的大小赋值给size,再判断size是否为0
if ((size = elementData.length) != 0) {
//如果转换后的elementData不是Object[]类型,则调用Arrays.copyOf()方法赋值
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果size为0,则将EMPTY_ELEMENTDATA赋值给内部容器elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
由源码看出,每次size判断为0时,都是将EMPTY_ELEMENTDATA赋值给了内部容器elementData。
(4) add()方法
public boolean add(E e) {
//第一步就是确定容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//把元素添加到末尾的位置
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果此时,是无参构造函数创建的ArrayList添加元素,那么设置DEFAULT_CAPACITY与minCapacity中大的那个数作为容量。也就是add()第一个元素的时候,容量就为10了。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//父类的属性,用于标记修改次数,在并发修改时,用于快速失败
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//动态扩容核心逻辑方法
grow(minCapacity);
}
private void grow(int minCapacity) {
//获取原容量oldCapacity
int oldCapacity = elementData.length;
//获取新容量newCapacity为原容量oldCapacity的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量newCapacity还是小于目标容量minCapacity,那么设置新容量就为目标容量minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量newCapacity大于MAX_ARRAY_SIZE,也就是Integer.MAX_VALUE-8,则调用hugeCapacity()方法
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//扩容成功后,复制旧数组到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//如果minCapacity < 0,也就是minCapacity已经超过了int的最大值Integer.MAX_VALUE,则抛出内存溢出错误
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断目标容量minCapacity是否大于MAX_ARRAY_SIZE,如果大于了,就返回Integer.MAX_VALUE,没有超过就返回MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
add()方法的扩容机制,总结起来分为以下两步:
* 添加一个元素,没超过默认容量大小10的时候,容量不变;超过后变为原来的1.5倍。
* 添加多个元素,超过了默认容量大小是,先扩容到原容量的1.5倍, 然后与目标容量做比较,谁大就用谁。然后进行边界判断,就是判断扩容后的容量是否已经大于了MAX_ARRAY_SIZE:如果小于MAX_ARRAY_SIZE,则还是用新扩容的容量;如果大于了MAX_ARRAY_SIZE,则需要对目标容量进行判断。a) 目标容量小于0,其实就是已经越界了,那么就直接抛出错误;b) 目标容量如果也大于了MAX_ARRAY_SIZE,则将新容量设置为Integer.MAX_VALUE;c) 若目标容量小于MAX_ARRAY_SIZE,则设置新容量为MAX_ARRAY_SIZE。
值得一提的是,ArrayList的元素插入操作,不是将对应位置的元素覆盖,而是将该位置的元素已经后面的元素全部向后移动,腾出目标位置给目标元素存放。所以这种操作的费时费力程度与插入元素离末尾位置的距离成正比,也就是说操作的位置里离末尾越远就越费时费力。
(5) add()系列的其他方法
//在指定坐标处添加元素
public void add(int index, E element) {
//下标校验调用方法rangeCheckForAdd(),判断是否越界
rangeCheckForAdd(index);
//下面的操作和add()方法基本一致
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//批量插入一个集合中的所有元素
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//从指定坐标index处开始,批量插入一个集合中的所有元素
public boolean addAll(int index, Collection extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//如果插入的位置是在中间
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
//插入操作时,进行下标校验
private void rangeCheckForAdd(int index) {
//插入目标位置index不能大于元素个数。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
操作步骤与add()方法也大致相同,但是在指定下标index处插入指定集合c时,需要先将index以及后面的元素向后移动c的元素个数,以保证能存下目标集合c所有的元素。
(6) set()方法修改元素
public E set(int index, E element) {
//惯例检查下标是否越界
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
//此处的判断规则是,需要修改的下标index只能小于容器元素个数size,和此容器容量无关。与add()方法的判断规则完全不同。此处也没有判断index>0,是因为elementData()方法数组取值时,可以直接抛出异常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//生成错误信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
//返回数组对应下标下的元素
E elementData(int index) {
return (E) elementData[index];
}
(7) remove()方法移除元素
public E remove(int index) {
//惯例检查是否越界,与set()用的一个方法
rangeCheck(index);
//监测修改次数,用于快速失败
modCount++;
//获取原来index坐标下的元素
E oldValue = elementData(index);
//判断移除的元素位置是否不在末尾
int numMoved = size - index - 1;
//numMoved>0表示移除的元素位置是不在末尾的
if (numMoved > 0)
//移除指定位置元素,并将后面的元素向前移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//如果在末尾,直接将最后一位的元素设置为null,清除后让GC回收
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
remove()方法不会引起数组的容量变化。
(8) remove()系列的其他方法
//移除指定元素
public boolean remove(Object o) {
//判断参数元素o是否为null,为null采用==比较,不为null采用equals比较
if (o == null) {
for (int index = 0; index < size; index++)
//如果相同则删除,然后return了,所以remove(Object o)方法只会删除集合第一个与传入对象相同的元素。
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//不为null采用equals比较
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//与remove(int index)一致,少了范围校验和返回旧元素
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
//移除fromIndex到toIndex的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
//移除集合c中包含的所有元素
public boolean removeAll(Collection> c) {
//调用Object的工具类Objects的requireNonNull()方法进行非空校验
Objects.requireNonNull(c);
return batchRemove(c, false);
}
//批量移除
private boolean batchRemove(Collection> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//如果elementData[r]这个元素不包含在集合c中,则将这个元素从坐标0开始往后放,也就是将不移除的元素前移
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//把坐标w到size的元素全部置为null,以便GC回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
需要注意的是,需要遍历ArrayList的时候,只能用它实例的迭代器进行迭代,不能直接采用for循环等直接比那里,会抛出异常。
(9) trimToSize()瘦身方法
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
这个方法会将自动扩容后,没有被元素填充的位置清除掉,也就是这个方法执行后,size应该与容量相等。
(10) isEmpty()方法
public boolean isEmpty() {
return size == 0;
}
判断ArrayList类的实例是否没有元素,但不是null,判断的是size是否为0。
(11) contains()方法
//判断是否含有目标元素O
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//返回元素o所在下标
public int indexOf(Object o) {
//判断是否为null,如果是则采用==判断
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
判断是否为null,如果不是则采用equals()方法判断
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//不存在则返回-1
return -1;
}
indexOf()方法也只是返回找到的第一个符合条件的元素的下标,后面还有也不管。
(12) lastIndexOf()方法
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
lastIndexOf()方法也只是返回找到的第一个符合条件的元素的下标,但是是从后往前找,前面还有也不管。
(13) clone()方法
public Object clone() {
try {
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList的本质是维护了一个Object的数组,所以克隆也是通过数组的复制实现的,属于浅复制。想要深克隆,需要循环克隆每个对象元素。
(14) clear()方法
public void clear() {
modCount++;
//循环设置每一个原有元素为null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
还有其他简单的方法就不讲解了。ArrayList类和AbstractList类类似,也有两个内部类 Iterator, ListIterator ,它们都是迭代器的实现类,分别为 Itr,ListItr;还有内部类SubList类这里再做一下简单的概括:
* Iterator类型的迭代器Itr对象,只能向后遍历,可以调用Itr.remove()移除元素,但是不能新增元素。
* ListIterator类型的迭代器ListItr对象相比Iterator类型的Itr对象要多一个向前遍历的功能,并且在Iterator的基础上,还能调用add()方法添加新的元素。
* SubList返回的是原集合的视图,也就是说,如果对 subList 出来的集合进行修改或新增操作,那么原始集合也会发生同样的操作。想要独立出来一个集合就需要重新new一个新的对象,代码如下:
List subList = new ArrayList<>(list.subList(0, 1));
三、总结
学习ArrayList类主要是关注它的动态扩容,以及size和容量间的区别。突然想到一题分享给大家,源码如下:
ArrayList l = new ArrayList(2);
l.add("1");
l.set(1,"2");
提问:这段代码执行后,l中变成什么内容?
答案:这是陷阱题,set()方法执行的时候会报错,虽然初始化指定了容量为2,但是add()方法执行后,size是1,set()方法是改变目标坐标的值,而坐标为1的地方是没有东西的,所以会抛出下标越界的异常。