本文上接ArrayList源码学习(一),将继续介绍ArrayList中的剩余部分,类中还包括一些私有内部类用于实现上层接口规定的迭代器iterator和子列表subList。
迭代器
按照List接口规范,列表支持两种迭代器:从集合继承来的普通迭代器和列表自身的允许双向遍历的迭代器。ArrayList类中使用class Itr和class ListItr自定义了两种迭代器的具体实现,也是对AbstractList中实现的优化。
Iterator
// 以适当的顺序返回此列表中元素的迭代器
public Iterator<E> iterator() {
return new Itr();
}
ArrayList通过上面方法返回该列表的迭代器对象,然后通过迭代器对象就能对列表进行遍历。
成员变量&构造器
private class Itr implements Iterator<E> {
int cursor; // 下一个将要返回的元素的索引
int lastRet = -1; // 上一个返回元素的索引,如果通过调用 remove 删除此元素,则重置为 -1
int expectedModCount = modCount; // 迭代器认为支持列表应该具有的 modCount 值。 如果违反此期望,则迭代器已检测到并发修改
Itr() {}
}
变量expectedModCount初始化为列表结构被修改的次数,在迭代器遍历过程中如果列表结构被修改,则快速失败中断遍历,将在下文源码中详细介绍。
迭代器操作
// 如果迭代器还有元素遍历,则返回true
public boolean hasNext() {
return cursor != size;
}
该方法判断是否迭代器是否遍历结束,此处使用游标cursor和列表长度比较,如果遍历结束将会使cursor == size。
// 返回迭代的下一个元素
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 校验并发修改,使用final定义,防止被重写,比较安全
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
next()是迭代器遍历列表的主要方法,首先就是执行贯穿整个迭代器的一个逻辑:校验并发修改,通过列表的modCount和迭代器创建时的expectedModCount比较,如果不相等就说明迭代器生成以后列表结构已经被修改,就要抛出ConcurrentModificationException中断遍历,这就是迭代器快速失败的主要逻辑。校验并发修改以后就进入正常的逻辑:
- 检查迭代器的游标值,如果大于列表
size,就表示遍历结束,没有将要返回的值,抛出NoSuchElementException; - 如果游标值大于列表数组的长度,则说明此时的列表的结构被修改过,抛出并发修改异常
ConcurrentModificationException; - 最后将
cursor递增,等于下一个元素的索引,并且返回当前元素,使lastRet赋值当前元素的索引。
// 从列表中移除迭代器返回的上一个元素,每次调用next()只能调用一次本方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
首先检查lastRet的值,正常遍历情况下,该值等于上一个返回元素的索引。如果小于0,说明过去没有调用过next()返回元素(或者已经执行过本方法),因此也没有元素可删除,抛出非法状态异常IllegalStateException,接着执行了检查并发修改的逻辑。
正常校验通过以后,调用外部类的remove()删除列表指定索引的元素。然后将lastRet的值赋给cursor,因为删除一个元素下一个要遍历的元素索引仍为当前索引,按照ArstractList文档规范,迭代器删除元素后,要将lastRet重置为 -1,这也说明了第一步产生lastRet < 0的另一种情况。最后一步不能忘掉更新expectedModCount为新的modCount,毕竟删除元素也会使列表结构修改,这样迭代器才能正常遍历下去。
而在此代码中,为了进一步防止并发修改,捕捉了所有IndexOutOfBoundsException,将抛出并发修改异常。
该迭代器中包含了另外一个遍历方法forEachRemaining用来一次性遍历剩余元素,代码逻辑比较清晰,在这不做源码解析。
ListIterator
// 返回此列表中元素的列表迭代器,从列表中的指定位置开始。 指定的索引指示初始调用next将
// 返回的第一个元素。 对previous的初始调用将返回指定索引减一的元素
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
// 返回此列表中元素的列表迭代器
public ListIterator<E> listIterator() {
return new ListItr(0);
}
以上是ArrayList中两种获取列表迭代器的方法,该列表迭代器是从上文普通迭代器继承而来,并实现了ListIterator的接口,因此向后遍历的逻辑和Itr中一样,新增了向前遍历的逻辑和一些其他方法。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
}
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public boolean hasPrevious() {
return cursor != 0;
}
向前遍历的方法和next()的实现逻辑类似,只是将游标cursor向前“滑动”。而当游标到0的时候就不能再进行previous遍历了。此外,还提供了两个获取前后元素索引的方法:
// 返回后续调用next将返回的元素的索引。如果列表迭代器位于列表末尾,则返回列表大小
public int nextIndex() {
return cursor;
}
// 返回后续调用previous将返回的元素的索引。如果列表迭代器位于列表的开头,则返回 -1
public int previousIndex() {
return cursor - 1;
}
其他方法
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
根据文档,该方法将元素游标cursor之前,对后续调用next()无影响,而调用previous()就会返回该元素。因此方法内,插入以后会将游标加 1,lastRet重置为 -1,并且更新expectedModCount的值。
// 将next或previous返回的最后一个元素替换为指定的元素(可选操作),
// 仅当在最后一次调用next或previous之后没有调用remove和add才能进行此调用
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
方法禁止在调用remove()和add()后调用,因此首先就检查了lastRet,如果被重置为 -1,就表示非法的状态。然后再检查并发修改,最后直接调用ArrayList的set()方法替换元素。
ArrayList中支持的迭代器操作比较简单,却非常方便的控制着列表的遍历访问操作,快速失败机制也非常严谨的控制着列表的修改。
子列表
子列表subList是列表List独有的操作性质,返回的子列表由父列表所支持,因此对子列表的非结构性修改会反映在父列表中,反之亦然。(结构性修改是那些改变这个列表的大小,或者以其他方式扰乱它,以致正在进行的迭代可能会产生不正确的结果)。在ArrayList中,子列表有内部类SubList实现,继承了AbstractList实现了列表基本的逻辑操作和迭代器操作。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
该方法是列表中唯一获取子列表的方法,通过参数两个索引获取在这个区间内元素组成的子列表。必要的,需要检查参数的有效性:起始索引不能小于 0,结束索引不能大于列表长度,并且起始索引不能比结束索引大。根据文档规定,如果fromIndex和toIndex相等,则返回的列表为空。然后通过参数构造一个子列表。
SubList
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}
其内部维护了一个父列表的AbstractList类型引用,parentOffset表示相较于父列表的偏移量,即等于子列表的 fromIndex,操作parent时会使用该值;而 offset 另外记录一个表示相较于顶级列表的偏移量,当使用子列表创建子列表的时候将会设置该值,否则等于 fromIndex;size则记录了该子列表的长度,此外还记录了modCount控制并发操作。
SubList的操作方法逻辑和ArrayList中基本相同,只不过操作的列表数组是顶级父列表中的数组,这里只介绍部分方法用于区分。
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, offset, fromIndex, toIndex);
}
get方法获取该子列表中的元素,校验索引和并发修改外的方法已经比较熟悉,而取值则是通过offset + index去ArrayList的数组中取。在add方法中,添加元素又通过持有的父列表对象添加。最后subList方法中创建子列表传入了偏移量offset,这是和ArrayList.subList()的唯一差别。通过这些能很好的看出parentOffset和offset两个变量的作用。
原文地址 --- Java容器源码学习分析专题——ArrayList源码学习(二)