本文会主要从集合扩容、添加元素、删除元素三个方面来进行源码解读
在开始解读之前,我们先来看一下ArrayList添加一个元素
的流程
基本在图中已经比较全面的说明了add一个元素的全流程
源码解读
我们先来看ArrayList中几个关键的字段
/**
* ArrayList默认的容量为10个Object
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList的数组缓冲区,添加第一个元素将扩容为DEFAULT_CAPACITY
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList中包含的元素数量
*
* @serial
*/
private int size;
这几个字段待会分析都会用到,需要都理解,接下来看下无参构造方法
/**
* 构造一个初始容量为10的空列表
*/
// eg1:初始化ArrayList,则elementData = {}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从这里开始,我们分析ArrayList添加一个方法的具体过程
/**
* 新增元素到数组末尾
*/
// eg1:第一次新增元素e="a1"
public boolean add(E e) {
// 确定是否需要扩容,如果需要则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// eg1:size=0,elementData[0]="a1",然后size自增为1
elementData[size++] = e;
return true;
}
先看下ensureCapacityInternal
方法,入参为size+1
// eg1:
private void ensureCapacityInternal(int minCapacity) {
// eg1:calculateCapacity方法返回10,ensureExplicitCapacity方法不需要扩容(minCapacity=size+1)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里调用了计算容量的方法calculateCapacity
,minCapacity=size+1
,看看calculateCapacity方法
/**
* 计算ArrayList容量
* <p>
* 如果elementData数组中没有一存储的元素,则返回默认值10(DEFAULT_CAPACITY)
* 否则返回minCapacity
*
* @param elementData 底层存储的ArrayList元素的数组
* @param minCapacity ArrayList中元素的个数
* @return
*/
// // eg1:第一次新增元素,minCapacity=1 elementData={}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// eg1:满足条件,返回10(DEFAULT_CAPACITY)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
看eg1,这里返回DEFAULT_CAPACITY
,也就是10,然后再看下ensureExplicitCapacity
方法
/**
* 确保明确的ArrayList的容量
*
* @param minCapacity ArrayList所需的最小容量
*/
// eg1:minCapacity=10
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果所需要的容量大于elementData数组的容量,则进行扩容操作
// eg1:10-0>0,满足扩容需求
if (minCapacity - elementData.length > 0)
// 扩容
grow(minCapacity);
}
这里是满足if条件,所以需要调用grow
方法
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* <p>
* 扩容操作
*
* @param minCapacity 所需的最小扩容容量
*/
// eg1:第一次扩容,minCapacity=10,满足第一个if条件,则将elementData数组扩容为10长度
private void grow(int minCapacity) {
// 原有数组elementData的长度
int oldCapacity = elementData.length;
// 新增oldCapacity的一半整数长度作为newCapacity的额外长度(右移一位代表除2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容后的长度依然无法满足最小容量minCapacity,则新的扩容直接为minCapacity
if (newCapacity - minCapacity < 0)
// newCapacity=10
newCapacity = minCapacity;
// 新的扩容长度newCapacity超出了最大的数组长度MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将旧数组中的元素赋值到新的扩容后的数组中
// eg1:newCapacity=10, 扩容elementData的length=10
elementData = Arrays.copyOf(elementData, newCapacity);
}
grow
方法也是主要的扩容逻辑,grow完成后返回到add方法,将扩容后的elementData(这时候是10),将elementData[0]="a1"
/**
* 新增元素到数组末尾
*/
// eg1:第一次新增元素e="a1"
public boolean add(E e) {
// 确定是否需要扩容,如果需要则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// eg1:size=0,elementData[0]="a1",然后size自增为1
elementData[size++] = e;
return true;
}
至此就完成了一次元素添加操作
接下来我们看下remove
方法,这个方法比较简单,代码如下:
/**
* 删除元素
*/
// eg1:elementData中保存了{"a1","a2","a3","a4"},删除第一个元素,index=0
public E remove(int index) {
// 校验传入的参数index是否超出了数组的最大下标,如果超出则抛出:ArrayIndexOutOfBoundsException
rangeCheck(index);
modCount++;
// 取出此下标元素
// eg1:oldValue="a1"
E oldValue = elementData(index);
// 需要一定的元素数量
// eg1:numMoved=4-0-1=3
int numMoved = size - index - 1;
if (numMoved > 0)
// 从需要删除的index后一位开始到末尾的这部分数据整体都向前移动一个元素(原有index的元素就被挤到了最后)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
// 将最后一位元素置为null,通知jvm将其垃圾回收
elementData[--size] = null;
// 返回已被删除的元素
return oldValue;
}
源码中多次出现的modCount
作用是什么?
在ArrayList中,所有修改集合元素的的操作都有modCount,这是在它的父类AbstractList,顾名思义,就是修改次数,接下来我们就聊聊modCount的作用
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
注释大致意思就是:
modCount 记录集合被的次数
。结构修改是那些改变列表大小的修改,或者以这样一种方式扰乱列表,使得进行中的迭代可能产生不正确的结果。
如果该字段的值发生意外变化,迭代器(或列表迭代器)将抛出ConcurrentModificationException
来响应next、remove、previous、set或add操作。这提供了fail-fast
行为,而不是在迭代期间面对并发修改时的非确定性行为。
通过子类使用该字段是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),那么它只需在add(int, E)、remove(int)方法(以及它覆盖的导致列表结构修改的任何其他方法)中增加该字段。
单个调用add(int, E)或remove(int)必须执行modCount++且不超过一个
,否则迭代器(和列表迭代器)将抛出ConcurrentModificationExceptions。如果实现不希望提供快速失败的迭代器,则可以忽略此字段。
主要作用
- 识别多线程并发修改并报错,有modCount一般都是线程不安全
- 对集合迭代过程中验证是否被修改,如果被修改则抛出:ConcurrentModificationException
源码解读
modCount主要用于避免迭代的时候集合被其它线程修改,这里我们看下AbstractList
中的私有类Itr
private class Itr implements Iterator<E> {
/**
* 调用next返回的索引
*/
int cursor = 0;
/**
* 上一个元素,如果通过调用remove删除该元素,则重置为-1。
*/
int lastRet = -1;
/**
* 迭代器的期望值为modCount,如果迭代过程中检查两个值不同,则认为被并发修改了
*/
int expectedModCount = modCount;
/**
* 是否有下一个元素
*/
public boolean hasNext() {
// cursor不等于数组长度则表示还有
return cursor != size();
}
/**
* 获取下一个元素
*/
public E next() {
//
checkForComodification();
try {
int i = cursor;
E next = get(i);
// 记录最后一个获取的元素
lastRet = i;
// 位置往前移一个
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
/**
* 移除元素
*/
public void remove() {
// 如果没有执行next,直接remove,则抛出IllegalStateException
if (lastRet < 0)
throw new IllegalStateException();
// 检查是否被修改
checkForComodification();
try {
// 移除next时记录的cursor的元素
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
// 重置最后一个next的元素位置
lastRet = -1;
// 重置期望的modeCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
/**
* 【重要】检查迭代过程中集合元素是否有被修改,被修改则抛出ConcurrentModificationException
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在代码中,如果没有执行next,直接调用remove,那么下次执行remove就会因lastRet=-1而抛出IllegalStateException
对非线程安全的集合操作推荐使用迭代器,迭代过程中会有进行多线程修改判断,否则非常容易发生数组越界
如果喜欢我的文章记得一定要
一键三连
哟(别下次一定!!