一、ArrayList简介
- ArrayList是一个数组队列,相当于动态数组,基于List接口实现。核心由Object[]数组组成,添加元素的时候,数组的长度会自动扩展。
- ArrayList不是一个线程安全类,与之相对应的是同样基于数组实现的线程安全集合类Vector,除线程安全外,他们最大的区别为动态扩展时ArrayList增长为原来的1.5倍,Vector为原来的2倍
- ArrayList的初始容量为10,增长因子为0.5
- ArrayList继承结构简图

二、ArrayList源码解析
1. 继承结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- AbstractList
- AbstractList中List对抽象类中的部分方法进行了实现,对子类中的方法进行了规范,同时也方便其扩展
- List
- 据作者说这是一个错误,因为AbstractList也同样实现了List接口,但因为没影响,就沿用至今
- RandomAccess
- 这是一个标记性接口,意义是: 在对列表进行随机或顺序访问的时候,访问算法能够选择性能最佳方式。 在for循环遍历的时候,能提高性能
- Cloneable
- 标明其可以实现克隆操作,主要为Object.clone()方法
- Serializable
- 序列化接口,标明其可以被序列化,主要用于字节流的传输
2. 属性
//序列化版本,主要用于序列化时判断版本是否一致
private static final long serialVersionUID = 8683452581122892189L;
//初始容量
private static final int DEFAULT_CAPACITY = 10;
//空对象数组,在后续代码需要时使用的空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//初始化空对象数组,在初始化时提供空数组对象(这个时候是没有长度的,会在add方法中初始化长度为10)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//核心元素数组,用于储存元素,不参与序列化
transient Object[] elementData;
//实际元素数组长度,默认为0
private int size;
//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//记录修改的次数,继承自AbstractList
protected transient int modCount = 0;
3. 构造方法
- 无参构造
/*
* 为核心元素数组赋值为初始化空对象数组,这个时候还未初始化长度,会在add方法中初始化长度为10
*/
public ArrayList() {
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA是个空的Object[],用它初始化elementData。
//这个空的Object[]的初始容量为10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 有参构造1
/*
* 带初始化容量大小的构造方法,如果数值合法,会将核心元素数组初始化为相应大小数组
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果该值大于0,为核心元素数组初始化相应大小的空数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果该值等于0,为核心元素数组初始化空对象数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果该值为其他值,抛出非法数据异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
- 有参构造2
/**
* 传入一个集合,通过这个集合来初始化ArrayList
* 集合中指定泛型的类型必须为ArrayList指定类型的子类
* 该类不常用
*/
public ArrayList(Collection<? extends E> c) {
//调用toArray()方法转化为数组,并赋值给核心元素数组
elementData = c.toArray();
//判断数组长度
if ((size = elementData.length) != 0) {
//每个集合的toArray()方法不一样
//如果不是Object[].class类型,则通过Arrays.copyOf将其改造为Object[].class类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果传入集合为空,为核心元素数组初始化空对象数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
4. 核心方法
4.1 add方法
-
boolean add(E e):在List末尾添加元素
- 判断是否需要初始化,需要的话执行初始化
- 判断是否需要扩展,需要的话调用grow方法,将原长度扩展到以前的1.5倍
- 数组最后一位放入传入元素,长度+1
/**
* 在List末尾添加元素
*/
public boolean add(E e) {
//确定容量是否足够,因为要添加一个元素,所以传入size+1进行判断
ensureCapacityInternal(size + 1);
//size+1,并将其放入正确位置
elementData[size++] = e;
return true;
}
/**
* 确定内部容量
*/
private void ensureCapacityInternal(int minCapacity) {
//先调用calculateCapacity判断实际需要内部容量大小
//再调用ensureExplicitCapacity将实际容量和需要容量对比,如果不足,执行增长
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 用于确定内部容量长度
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//先判断有没有初始化核心数组的容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回值为minCapacity和DEFAULT_CAPACITY的较大值,一般情况下返回10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果初始化过了则返回传入的容量
return minCapacity;
}
/**
* 判断实际容量是否满足add之后的容量大小,如果不满足,则执行增长
*/
private void ensureExplicitCapacity(int minCapacity) {
//修改次数+1
modCount++;
//如果所需长度大于实际长度,执行增长
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* ArrayList的核心方法,用于扩展数组大小。
*/
private void grow(int minCapacity) {
//将扩充前的elementData的长度赋值给oldCapacity
int oldCapacity = elementData.length;
//将新的容量newCapacity扩充到oldCapacity的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这种情况主要出现在elementData=EMPTY_ELEMENTDATA时,即newCapacity=0时
//将新的长度设置为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果newCapacity大于最大容量,调用hugeCapacity赋予最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 新的容量已经确定好了,则调用copyOf,改变容量大小完成扩展
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 用于赋最大值
*/
private static int hugeCapacity(int minCapacity) {
//传入值大于0,不多解释
if (minCapacity < 0)
throw new OutOfMemoryError();
//从MAX_ARRAY_SIZE初始化中可以看到MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
//如果minCapacity大于MAX_ARRAY_SIZE,则返回MAX_ARRAY_SIZE,相当于两层防护
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
-
void add(int index, E element):在指定位置添加元素
- 判断插入位置是否合理
- 判断是否需要初始化,需要的话执行初始化
- 判断是否需要扩展,需要的话调用grow方法,将原长度扩展到以前的1.5倍
- 执行System.arraycopy方法对原数组执行添加并扩展操作
/**
* 在指定位置添加元素
*/
public void add(int index, E element) {
//判断插入位置是否合理
rangeCheckForAdd(index);
//确定容量,同上
ensureCapacityInternal(size + 1);
//将index位以及之后的元素依次往后挪一位
//5个参数意义分别是:
//源数组,源数组要复制的起始位置,目标数组,目标数组粘贴的起始位置,进行copy的数组长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将输入元素放入正确位置
elementData[index] = element;
//长度增长1
size++;
}
/**
* 判断插入位置是否合理
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- boolean addAll(Collection<? extends E> c)、boolean addAll(int index, Collection<? extends E> c)
- 以上两种方法分别是在集合末尾或指定位置添加集合类元素
- 这两种方法的底层实现和add(int index, E element)类似,都是:
- 先判断是否需要初始化或扩展,需要的话执行初始化或扩展
- 再执行System.arraycopy方法对原数组执行添加并扩展操作
- 可自行查看源码,故不再累述
- 总结
- add方法可以在List末尾添加元素或者在指定位置添加元素
- 为默认空数组初始化默认长度10的过程在add方法中
- add方法会在容量不够的时候调用grow方法,默认扩展到原来的1.5倍,特殊情况下:
- 原长度为0,则赋值初始值
- 新扩展数组大小已经达到最大值,只会扩容到Integer.MAX_VALUE
4.2 remove方法
- E remove(int index):移除指定位置元素,并返回该元素的值
- 检查取值是否合理
- 执行System.arraycopy方法对原数组执行删除并缩短操作
- 将最后一位置为null,方便GC回收
/**
* 移除指定位置元素,并返回该元素的值
*/
public E remove(int index) {
//判断index取值是否合理
rangeCheck(index);
//修改次数+1
modCount++;
//取出index位下标元素的值,便于返回
E oldValue = elementData(index);
//计算出进行copy的数组长度
int numMoved = size - index - 1;
//如果本来就是最后一位,则不进行复制,提高效率
if (numMoved > 0)
//执行数组复制,将index后一位以及之后的元素全部前移一位,原理同上
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//将最后一位置为null,以便于GC能在整个数组不使用时进行回收
elementData[--size] = null;
//返回index位元素的值
return oldValue;
}
/**
* 判断index取值是否合理
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 获取当前下标元素的值
*/
E elementData(int index) {
return (E) elementData[index];
}
- boolean remove(Object o):移除指定元素,返回是否成功(如果有相同元素,只移除第一个)
- 找到指定元素的index
- 执行fastRemove(int index)移除指定元素,这是一个和remove(int index)一样但无返回值的私有方法
/**
* 移除指定元素,返回是否成功
*/
public boolean remove(Object o) {
//先判断是否为空,主要是用于通过不同的方式判断元素是否存在
if (o == null) {
//遍历找出为空的元素下标,调用fastRemove将其移除,并返回true
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//遍历找出为Object o的元素下标,调用fastRemove将其移除,并返回true
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
//未找到该元素,返回false
return false;
}
/**
* 移除指定位置元素,相当于没有返回值的E remove(int index)
*/
private void fastRemove(int index) {
//修改次数+1
modCount++;
//取出index位下标元素的值,便于返回
int numMoved = size - index - 1;
//如果本来就是最后一位,则不进行复制,提高效率
if (numMoved > 0)
//执行数组复制,将index后一位以及之后的元素全部前移一位,原理同上
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//将最后一位置为null,以便于GC能在整个数组不使用时进行回收
elementData[--size] = null;
}
- boolean removeAll(Collection<?> c):批量删除,移除指定集合内包含的所有元素
- 先验证传入元素合法性
- 再将不重复的n位元素放在数组前n位
- 将n位以后的的元素置为null,完成批量删除
/**
* 批量删除,移除指定集合内包含的所有元素
*/
public boolean removeAll(Collection<?> c) {
//Objects工具类方法,判断传入的集合c是否为null
Objects.requireNonNull(c);
//调用并返回批量删除方法,删除相应元素
return batchRemove(c, false);
}
/**
* 批量删除方法
* complement为false用于removeAll方法,用于执行批量删除
* complement为true用于retainAll方法,用于判断两个集合是否有交集
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
//定义一个内部变量,用于保存原核心元素数组,将其记为M
final Object[] elementData = this.elementData;
//其中r用来控制循环,w用来记录有多少个交集元素
int r = 0, w = 0;
//初始化返回值
boolean modified = false;
try {
//遍历
for (; r < size; r++)
//如果complement为false,removeAll调用:
//则M的前w位元素为两个集合不同的元素,用于移除
//如果complement为true,retainAll调用:
//则M的前w位元素为两个集合相同的元素,用于交集
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// r != size的情况只会发生在遍历过程中发生异常,即contains方法发生异常时
if (r != size) {
//将异常点前遍历操作取出的元素,和异常点后剩下的元素拼接起来形成新的数组
//如果removeAll调用,新数组为异常点前不重复的元素和异常点后的元素
//即只执行了异常点前的移除操作
//如果retainAll调用,新数组为异常点前重复的元素和异常点后的元素
//即只执行了异常点前取交集,再拼接异常点后元素
System.arraycopy(elementData, r, elementData, w, size - r);
//这一步主要用于之后的循环判断置为null的元素位数
w += size - r;
}
//如果w == size,此时集合任为原集合
//如果removeAll调用,证明两个集合没有交集,移除后任为M本身
//如果retainAll调用,证明c中包含M,交集为M
if (w != size) {
//这一系列操作后,集合中只会留下相应的非交集(removeAll)或交集元素(retainAll)
//主要取决于complement的取值
for (int i = w; i < size; i++)
//置为null方便GC
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
//removeAll执行结束后,移除了元素返回true,否则返回false
//retainAll执行结束后,只有证明c中包含M或者相等时返回false,启用情况返回true
//故一般retainAll不能通过返回值来判断是否取交集成功,应该按照操作后元素个数来进行判断
return modified;
}
- 总结
- remove系列方法先把不重复的n个元素放在前数组的前n位
- 然后把n位以后的元素置为null,方便GC调用
4.3 set方法
- E set(int index, E element):为相应下标元素设置相应的值
- 先验证下标合法性
- 再赋予新值,返回旧值
/**
* 为相应下标元素设置相应的值
*/
public E set(int index, E element) {
//检查index是否合理
rangeCheck(index);
//取出旧值
E oldValue = elementData(index);
//赋予新值
elementData[index] = element;
//返回旧值
return oldValue;
}
- 总结
- 核心为数组的设值操作
4.4 get方法
- E get(int index):返回相应下标的值
- 先验证下标合法性
- 再返回相应下标的值
/**
* 返回相应下标的值
*/
public E get(int index) {
//检查index是否合理
rangeCheck(index);
//返回相应下标的值
return elementData(index);
}
- 总结
- 核心为数组的取值操作
4.5 indexOf方法
- int indexOf(Object o):返回传入对象的位置,如果有重复的对象,只会返回第一个的位置
- 遍历取出传入对象的位置
/**
* 返回传入对象的位置
*/
public int indexOf(Object o) {
//先判断是否为空,主要是用于通过不同的方式判断元素是否存在
if (o == null) {
//返回第一个为null对象的位置
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//返回第一个和o相等的对象的位置
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//如果没找到就返回-1
return -1;
}
- 总结
- 核心思想就是遍历,比较,返回位置
4.6 retainAll方法
- boolean retainAll(Collection<?> c):求交集,移除非交集元素
- 先验证数据合理性
- 再调用batchRemove移除非交集元素,剩下交集元素
/**
* 求交集,执行后集合剩下交集元素
*/
public boolean retainAll(Collection<?> c) {
//Objects工具类方法,判断传入的集合c是否为null
Objects.requireNonNull(c);
//调用并返回批量删除方法,删除非交集元素,具体参考removeAll
return batchRemove(c, true);
}
- 总结
- retainAll重复的n位元素放在数组前n位,然后将n位以后的的元素置为null,完成求交集
- retainAll不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交集。
4.7 clear方法
- void clear():清空集合
- 将集合中所有值置为null
- 将集合size置为0
public void clear() {
//修改次数+1
modCount++;
//所有对象置为null,断开引用,gc回收这些对象
for (int i = 0; i < size; i++)
elementData[i] = null;
//数组长度重置为0
size = 0;
}
- 总结
- clear()会将所有对象置为null,即断开这些对象的引用,以便于GC回收他们
- clear()之后核心元素数组仍会存在于堆内存中,仍被引用,不会被GC回收,故任可调用add等方法进行操作
三、总结
- ArrayList可以存放重复元素,也可以存放空值,但是执行remove、indexOf等一系列方法时,都只会操作重复元素的第一个
- ArrayList本质上就是一个elementData数组,但其可以动态扩展大小,在add系列方法中调用grow方法进行扩展
- ArrayList默认长度为10,也可以通过带参数的构造方法自定义初始长度
- ArrayList默认最大长度为Integer.MAX_VALUE - 8,特殊情况下,也可能扩展到Integer.MAX_VALUE
- ArrayList默认增长因子为0.5,每次动态扩展默认扩展到原来的1.5倍,不可修改
- ArrayList的核心是数组,这也就导致了它在查询方面优势明显,但插入和删除的时候性能会有较大劣势,所有查询多用ArrayList,修改多用LinkedList
- ArrayList实现了RandomAccess,所以在遍历它的时候使用for循环性能较好。
本文深入解析ArrayList的内部实现,包括其动态扩展机制、初始化过程、核心方法如add、remove、set、get等的工作原理,以及ArrayList与LinkedList在性能上的对比。

被折叠的 条评论
为什么被折叠?



