浅析 ArrayList 源码(JDK 1.8)
1. ArrayList 的继承类图
Serializable
接口:标记接口,实现此接口被标记为可序列化的。RandomAccess
接口:标记接口,实现此接口被标记为支持快速随机访问。Cloneable
接口:标记接口,实现此接口被标记为可克隆的。List
接口:常规接口,实现此接口可以使用对有序集合的增删改查等基础操作。
2. ArrayList 的成员变量
/**
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要膨胀多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储数组列表元素的数组缓冲区
* 数组列表的容量是此数组缓冲区的长度
* 添加第一个元素时,任何符合 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 条件的空 ArrayList 都需要将数组列
* 表的初始大小扩展到 DEFAULT_CAPACITY
*/
transient Object[] elementData; // 非私有,以简化嵌套类访问
/**
* 数组列表的大小(它包含的元素数)
*/
private int size;
/**
* 数组的列表的最大容量
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
注意:
elementData
是被transient
关键字修饰,不会被序列化为字节流。
3. ArrayList 的自定义序列化
// 序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 首先获取modCount,防止并发操作
int expectedModCount = modCount;
// 调用ObjectOutputStream的方法,将当前类没有被修饰transient和不是static的字段写入流中
// 因为elementData被transient修饰了,所以这里不会写入
s.defaultWriteObject();
// 写出elementData数组大小size
s.writeInt(size);
// 根据size值大小遍历elementData数组,即仅写出非null的元素
// 因为一般数组扩容后,数组会自动补充elementData.length() - size数量的null值
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
// 判断当前modCount值与开始取得值是否一致,若不等于,说明在此期间,已有其余线程对此ArrayList进行操作,抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
// 反序列化
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 初始化elementData
elementData = EMPTY_ELEMENTDATA;
// 调用ObjectInputStream的方法,从流中读取非static字段以及非transient字段
s.defaultReadObject();
// 读取数组的容量
s.readInt(); // ignored
// 判断数组是否存在元素
if (size > 0) {
// 计算数组容量,因为elementData = EMPTY_ELEMENTDATA,所以永远有capacity=size
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
// 根据size大小,对elementData进行扩容
ensureCapacityInternal(size);
Object[] a = elementData;
//将数据依次读取
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
4. ArrayList 的构造函数
4.1 ArrayList(int initialCapacity) 构造函数
/**
* 构造具有指定初始容量的空列表
*
* @param initialCapacity – 列表的初始容量
* @throws IllegalArgumentException – 条件为传入列表的初始容量为负数
*/
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);
}
}
此构造函数通过对传入 initialCapacity
进行校验,得到不同elementData解决方案
initialCapacity
> 0,elementData
初始化为容量为initialCapacity
的Object
的数组initialCapacity
= 0,将elementData
赋值为空数组EMPTY_ELEMENTDATA
initialCapacity
< 0,抛出非法容量Illegal Capacity
异常
4.2 ArrayList() 无参构造函数
/**
* 构造初始容量为 10 的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 将
elementData
赋值为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。
4.3 ArrayList(Collection<? extends E> c) 构造函数
/**
* 按照集合的迭代器返回的顺序构造包含指定集合的元素的列表
*
* @param c – 其元素要放入此列表的集合
* @throws NullPointerException – 条件为传入的集合为空
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 可能(错误地)不返回 Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
-
将传入集合
c
转换为Object
数组,并赋值给elementData
。 -
判断
elementData
长度是否为0。-
若长度为0,则将
elementData
替换为空数组EMPTY_ELEMENTDATA
。 -
若长度不为0,进行
elementData
类型的判断,因为toArray
可能(错误地)不返回Object[]
,若判断为否,则通过Arrays.copyOf
将elementData
转换为Object[]
类型。
-
5. ArrayList 的扩容机制
- 在
ArrayList
中,如果需要添加元素,会使用ensureCapacityInternal
方法来确保当前容量足够,如果不够则需要进行扩容操作。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
进行添加元素操作时,
minCapacity
传值为size
+ 添加元素数量
- 首先使用
calculateCapacity
计算elementData
需要的最小容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断elementData是否被初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
- 再调用
ensureExplicitCapacity
来确保elementData
当前的数组长度是否满足计算得到的最小容量。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 不满足则调用
grow
方法进行扩容
/**
* 增加容量以确保它至少可以容纳最小容量参数指定的元素数.
*
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// >> 的数学意义相当于除2,此处右移1位,表示oldCapacity除以2的1次方
// 因此ArrayList的扩容是默认将原数组扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 当插入大量元素时,即addAll方法,传入的minCapacity远大于1.5倍扩容的newCapacity,则直接赋值为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 判断扩容后的容量是否大于MAX_ARRAY_SIZE
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
6. 检查传入索引是否越界通用方法
6.1 rangeCheck(int index)
在 ArrayList
中,get(int index)
、set(int index, E element)
以及 remove(int index)
进行了调用。
/**
* 检查给定索引是否在范围内。如果不是,则引发相应的运行时异常
* 此方法不检查索引是否为负数:它总是在数组访问之前使用,如果索引为负数,则会抛出ArrayIndexOutOfBoundsException
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
6.2 rangeCheckForAdd(int index)
在 ArrayList
中,add(int index, E element)
以及 addAll(int index, Collection<? extends E> c)
进行了调用。
/**
* add and addAll 使用的 rangeCheck 版本
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
7. ArrayList 的增加方法
7.1 add(E e)
/**
* 将指定的元素追加到此列表的末尾
*
* @param e 要附加到此列表的元素
* @return true
*/
public boolean add(E e) {
// 判断是否需要扩容,是则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
7.2 add(int index, E element)
/**
* 在此列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到其索引中)
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException
*/
public void add(int index, E element) {
// 检查传入索引是否合法
rangeCheckForAdd(index);
// 判断是否需要扩容,是则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 此数组拷贝方法为native本地方法
// 旨在将elementData中从index索引开始到size-index个元素的索引对应元素向后移动index+1-index位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将元素插入到当前下标的位置
elementData[index] = element;
size++;
}
7.3 addAll(Collection<? extends E> c)
/**
* 将指定集合中的所有元素追加到此列表的末尾,顺序与指定集合的迭代器返回这些元素的顺序相同。
* 如果在操作过程中修改了指定的集合,则未定义此操作的行为。
* (这意味着如果指定的集合是此列表,并且此列表为非空,则此调用的行为是未定义的)
*
* @param c 包含要添加到此列表中的元素的集合
* @return 如果此列表因调用而更改,则为 true
* @throws NullPointerException 如果指定的集合为空
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// 判断是否需要扩容,是则进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 从源数组a的0索引起始向目标数组elementData的size索引起始复制numNew个元素
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
7.4 addAll(int index, Collection<? extends E> c)
/**
* 从指定位置开始,将指定集合中的所有元素插入到此列表中
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加其索引)
* 新元素将按照指定集合的迭代器返回的顺序显示在列表中
*
* @param index 插入指定集合中的第一个元素的索引
* @param c 包含要添加到此列表中的元素的集合
* @return 如果此列表因调用而更改,则为 true
* @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 > size())
* @throws NullPointerException 如果指定的集合为空
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查传入索引是否合法
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
// 判断是否需要扩容,是则进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 当 0 <= index < size 时
// 从源数组elementData的index索引起始向目标数组elementData的index + numNew索引起始复制numMoved个元素
// 即将elementData的index索引到size-index个元素的索引对应元素往后移index+numNew-index位
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 从源数组a的0索引起始向目标数组elementData的index索引起始复制numNew个元素
// 当 0 <= index < size 时,此copy是将a插入elementData的index到index+numNew
// 当 index = size 时,此copy是将a放入elementData的size后面,即末尾
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
8. ArrayList 的删除方法
8.1 remove(int index)
/**
* 删除此列表中指定位置的元素。将任何后续元素向左移动(从其索引中减去一个)
*
* @param index 要删除的元素的索引
* @return 从列表中删除的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
*/
public E remove(int index) {
// 检查传入索引是否合法
rangeCheck(index);
modCount++;
// 获取elementData中index下标对应元素,即需要被remove的元素
E oldValue = elementData(index);
int numMoved = size - index - 1;
// 当index = size - 1时,即删除数组最后一个元素,此时elementData不需要进行copy操作
if (numMoved > 0)
// 当 0 <= index < size - 1时,
// 从源数组elementData的index+1索引起始向目标数组elementData的index索引起始复制numMoved个元素,
// 即将elemetData中从索引index+1到size-1的元素位移index-(index+1)=-1位,也就是左移1位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组最后一位的引用置null,对象不再有指向,被GC回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
8.2 remove(Object o)
/**
* 从此列表中移除首次出现的指定元素(如果存在)。
* 如果列表不包含该元素,则它不变。
* 更正式地说,删除具有最小索引 i 的元素,使得(o==null ? get(i)==null : o.equals(get(i)))(如果存在这样的元素)。
* 如果此列表包含指定元素,则返回 true(或者等效地,如果此列表因调用而更改)。
*
* @param o 要从此列表中删除的元素(如果存在)
* @return 如果此列表包含指定的元素,则为 true
*/
public boolean remove(Object o) {
// 数组顺序排序,0~size,删除首个匹配的元素
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* 跳过边界检查并且不返回被删除值的私有删除方法。
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
// 当index = size - 1时,即删除数组最后一个元素,此时elementData不需要进行copy操作
if (numMoved > 0)
// 当 0 <= index < size - 1时,
// 将elemetData中从索引index+1到size-1的元素位移index-(index+1)=-1位,也就是左移1位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组最后一位的引用置null,对象不再有指向,被GC回收
elementData[--size] = null; // clear to let GC do its work
}
8.3 removeAll(Collection<?> c)
/**
* 从此列表中删除指定集合中包含的所有元素
*
* @param c 包含要从此列表中删除的元素的集合
* @return true 如果此列表因调用而更改
* @throws ClassCastException 如果此列表元素的类与指定的集合不兼容(可选)
* NullPointerException 如果此列表包含 null 元素,并且指定的集合不允许 null 元素(可选),
* 或者指定的集合为 null
*/
public boolean removeAll(Collection<?> c) {
// 对c集合进行判空
Objects.requireNonNull(c);
// 批处理移除,通过此方法的第二参数进行控制删除规则
return batchRemove(c, false);
}
8.4 retainAll(Collection<?> c)
/**
* 仅保留此列表中包含在指定集合中的元素
* 换句话说,从此列表中删除指定集合中未包含的所有元素
*
* @param c 包含要保留在此列表中的元素的集合
* @return true 如果此列表因调用而更改
* @throws ClassCastException 如果此列表元素的类与指定的集合不兼容(可选)
* NullPointerException 如果此列表包含 null 元素,并且指定的集合不允许 null 元素(可选),
* 或者指定的集合为 null
*/
public boolean retainAll(Collection<?> c) {
// 对c集合进行判空
Objects.requireNonNull(c);
// 批处理移除,通过此方法的第二参数进行控制删除规则
return batchRemove(c, true);
}
8.5 batchRemove(Collection<?> c, boolean complement)
/**
* 批量删除指定集合中的元素
*
* @param c 包含要保留在此列表中或删除的元素的集合
* @param complement 是否删除与集合中的元素不匹配的元素,true,则删除c不匹配的元素,false,则删除c匹配的元素
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
// 获取元素数组
final Object[] elementData = this.elementData;
// r为读指针,w为写指针,modified为是否修改了列表
int r = 0, w = 0;
boolean modified = false;
try {
// 遍历元素数组
for (; r < size; r++)
// 如果集合包含该元素且complement参数为true,或者集合不包含该元素且complement参数为false
if (c.contains(elementData[r]) == complement)
// 将该元素从新数组中删除,就是将保留的元素从索引0开始覆盖
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 一般情况下,r是等于size的,但删除元素时c.contains有可能发生异常
if (r != size) {
// 根据此时的r和w来判断,保留elementData遍历到r索引及后面的元素
// 还有保留符合c.contains(elementData[r]) == complement这个条件的元素
System.arraycopy(elementData, r,
elementData, w,
size - r);
// 更新w值,以方便后续索引对应元素置null
w += size - r;
}
if (w != size) {
// clear to let GC do its work
// 将数组w索引后的元素引用置null,对象不再有指向,被GC回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
// 更新数组大小size
size = w;
modified = true;
}
}
return modified;
}
8.6 removeIf(Predicate<? super E> filter)
/**
* 删除列表中所有满足过滤条件的元素
*
* @param filter 过滤条件
* @return 如果列表中的元素发生了变化,返回true
* @throws NullPointerException 如果filter为null
*/
@Override
public boolean removeIf(Predicate<? super E> filter) {
// 确保传入的过滤器不为null
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
// 记录需要删除的元素个数及其下标
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
// 记录当前modCount的值,用于在遍历过程中检测集合是否被修改
final int expectedModCount = modCount;
// 记录当前集合中元素的个数
final int size = this.size;
// 遍历集合中的元素,找到需要删除的元素
for (int i = 0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
// 如果当前元素满足过滤条件
if (filter.test(element)) {
// 标记该元素需要被删除,并统计需要删除的元素数量
removeSet.set(i);
removeCount++;
}
}
// 检查集合在遍历过程中是否被修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0; // 是否有元素需要被删除
if (anyToRemove) {
final int newSize = size - removeCount; // 新的列表大小
for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
// 找到下一个不需要删除的元素,并将其移动到左侧空出的位置
/**
* 假设size为5,且i=1、3时的元素需要删除
* 则bitSet中元素可以简单的理解为:
* 索引0-false
* 索引1-true
* 索引2-false
* 索引3-true
* 索引4-false
* 索引5-true
* nextClearBit(int fromIndex)可以简单理解为获取当前fromIndex及其后面首个为false的索引值
* length()可以简单理解为最后一个true的索引值+1,因此这里的length为4
* 当fromIndex=0,得0
* 当fromIndex=1,得2
* 当fromIndex=2,得2
* 当fromIndex=3,得4
*/
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
// 将右侧被删除的元素置为null,以便GC清理
for (int k = newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
// 更新列表大小
this.size = newSize;
// 检查modCount值是否发生变化
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// 更新modCount
modCount++;
}
// 返回列表中的元素是否发生了变化
return anyToRemove;
}
9. ArrayList 的修改方法
9.1 set(int index, E element)
/**
* 将此列表中指定位置的元素替换为指定的元素
*
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 先前位于指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
*/
public E set(int index, E element) {
// 判断index是否合法
rangeCheck(index);
// 获取源数组中的元素
E oldValue = elementData(index);
// 替换元素
elementData[index] = element;
return oldValue;
}
10. ArrayList 的查询方法
10.1 get(int index)
/**
* 返回此列表中指定位置的元素
*
* @param index 要返回的元素的索引
* @return 此列表中指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
*/
public E get(int index) {
// 判断index是否合法
rangeCheck(index);
return elementData(index);
}