ArraysList 基于动态数组实现了 List 接口。实现了所有的列表相关操作,它允许操作所有的元素(包括 null)。ArrayList 几乎和 Vector 一样,区别在于 ArrayList 是非线程安全的。本文从源码的角度分析 ArrayList 的实现细节。
jdk1.8.0_231
类签名
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}
ArrayList 的类签名可以看出,它没有被 final 修饰,说明是可被继承的;它继承了 AbstractList 并实现了若干接口。
其中它既继承了 AbstractList 类,又实现了 List 接口,而 AbstractList 本身已经实现了 List 接口。这里对 List 接口的继承看起来是多余的。从语法角度看,它确实是多余的,但是多了这么一点代码,使得 ArrayList 的意图更加清晰,生成的 Java doc 能够明确显示它是一个 List。JDK 源码中又很多类似的操作。[关于这个问题的讨论:Why do many Collection classes in Java extend the abstract class and implement the interface as well?]
另外,它还实现了 RandomAccess 接口,这个接口不提供任何抽象方法,只是一个标记,表示当前实现类支持高速的随机访问。即可以块速定位到指定下标的元素。ArrayList 通过 get 方法在 O(1) 时间复杂度内获取元素;类似地,LinkedList 中也可以通过 get 方法定位元素,但不是随机访问,而是迭代指定次数来定位元素,时间复杂度为 O(n),因此 LinkedList 也没有实现 RandomAccess 接口。
构造方法
ArrayList 提供了 3 个 public 构造方法:
方法 | 说明 |
---|---|
public ArrayList(int initialCapacity) | 构造一个空的 ArrayList 并且将初始容量设置为 initialCapacity,立即初始化成员变量 elementData |
public ArrayList() | 构造一个空的 ArrayList 并且将初始容量设置为 10,但不立即初始化 elementData |
public ArrayList(Collection<? extends E> c) | 构造一个 ArrayList 并且放入 c 中的元素,放入顺序与迭代 c 的顺序一致 |
ArrayList 使用一个名为 elementData 的 Object 数组作为存储结构,该数组长度即为 ArrayList 的容量。elementData 的定义如下所示。它被 transient 修饰,意味着序列化 ArrayList 时它会被忽略掉;它的访问修饰符为默认,意味着同包类可以直接访问该数组。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
ArrayList(int initialCapacity) 会立即对 elementData 进行初始化,构造一个长度为 initialCapacity 的数组并赋值给 elementData。ArrayList() 不会立即构造数组,而是将一个空的数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData,后面有数据插入的时候才构造一个长度为 10 的数组并赋值给 elementData。关于这两个构造方法的设计,这里有更详细的描述。
第 3 个构造方法如下。逻辑比较简单,如果传入的集合元素数量为 0 ,则 elementData 引用 EMPTY_ELEMENTDATA,和 new ArrayList(0) 的效果一样;如果传入的集合元素数量非 0,则直接让 elementData 引用集合构造出来的数组。这里有一个细节,它对 c.toArray() 返回的实例的类型做了一次检查,如果类型不是 Object[].class,就将其复制到 Object 数组中。这是为了修复之前的一个 bug,注释中的 6260652) 是 bug 编号。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
导致这个 bug 原因是 c.toArray() 返回的可能不是 Object[],如果不是 Object,且 c 中的元素是 E 的子类,不检查类型的话后期往 List 中放入 E 类型元素时会抛出 ArrayStoreException 异常。如下代码,bug 修复之前,执行 list.add(new Parent()) 时会抛出 ArraysStoreException。
class Parent {}
class Child extends Parent{}
class ArrayTest{
public static void main(String[] args){
List<Parent> list = new ArrayList<>(Arrays.asList(new Child(), new Child()));
list.add(new Parent());
}
}
增
方法 | 说明 |
---|---|
public boolean add(E e) | 新增一个元素,并返回 true |
public void add(int index, E element) | 将元素 element 插入到下标为 index 的位置,index 位置及后面的元素向后移 |
public boolean addAll(Collection<? extends E> c) | 将 c 中的元素添加到 ArrayList 末尾,添加的顺序与 c 的迭代顺序一致 |
public boolean addAll(int index, Collection<? extends E> c) | 将 c 中的元素插入到 index 位置,index 位置及其后面的元素后移,添加顺序与 c 迭代顺序一致 |
首先是最简单的 add(E e)
方法,这个方法先调用了 ensureCapacityInternal 确保数组的容量足够,然后将元素 e 放到 size 位置并返回 true。ensureCapacityInternal 放到后面再看,这里只需要知道他是确保 elementData 数组的长度足以放下 e 即可。
注释 "Increments modCount!!" 表示在调用 ensureCapacityInternal 的时候会对成员变量 modCount 进行 +1 操作。modCount 和后面的迭代器有关,只要对 ArrayList 对象进行了增、删、改操作,这个变量的值都会 +1,后面会介绍其作用。
size 变量值表示存放下一个元素的下标,也表示了当前 ArrayList 中元素的数量。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add(int index, E element)
指定了增加元素的位置,它先调用 rangeCheckForAdd 检查了传入的 index 是否在允许范围之内,index范围必须为:[0,size]
;然后确保 elementData 数组容量足够;再调用了 System.arraycopy 方法将 index 及其后面的元素复制到 index+1 起始位置,最后将元素放到 index 位置,更新 size。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
addAll(Collection<? extends E> c)
和 addAll(int index, Collection<? extends E> c)
与上面两个方法的套路类似,插入之前都需要检查下标是否越界,确保容量足够,往中间插入时也是先调用 System.arrycory 挪动后面的元素,再插入。modCount 也会发生变化,需要注意的是,尽管 addAll 一次性可添加多个元素,但 modCount 还是只 +1。这进一步反映了只需要关注 modCount 值的变化,而不需要关注它的具体值。
删
方法 | 说明 |
---|---|
public E remove(int index) | 将下标为 index 的元素移除 |
public boolean remove(Object o) | 将元素 o 移除,如果 o 为 null,则移除下标从小到大遇见的第一个元素,返回 true;否则移除与 o 相等(equals 返回 true) 的第一个元素,返回 true。若元素不存在,返回 false |
public boolean removeAll(Collection<?> c) | 移除存在于 c 中的元素 |
public boolean retainAll(Collection<?> c) | 移除不存在于 c 中的元素 |
public boolean removeIf(Predicate<? super E> filter) | 移除所有符合 filter 条件的元素 |
public void clear() | 将 ArrayList 中所有的元素移除 |
E remove(int index)
是根据下标移除一个元素,ArrayList 中,凡是要用到下标的公共方法,都会检查下标是否越界。同样,这个方法也会修改 modCount,然后调用 elementData 取出 index 的值,如果移除的不是最后一个元素,则调用 System.arraycopy 将 index 后面的所有元素往前移动一个下标位置。最后返回移除的元素。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1; // 这句有点迷,
if (numMoved > 0) // 为什么不直接用 if(index != size-1) 判断
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 将 size-1 位置的值设置为 null,将删除了的元素的引用从 elementData 中断开,让 GC 能够回收该对象。
return oldValue;
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
boolean remove(Object o)
通过下标从小到大遍历的方式找到第一个与 o 相等的元素的下标,然后调用私有方法 fastRemove(int index)
方法来移除元素。注意这里没有去调用 remove(int index) 方法,原因是这里不需要检查下标是否越界,调用 fastRemove 可以少一步判断。
public boolean remove(Object o) {
if (o == null) { // o 为 null 的情况
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index); // 调用 fastRemove(int index) 不用进行下标越界判断,不用返回 element
return true;
}
} else { // o 不为 null 的情况
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) { // 与 E remove(int index) 的区别是返回 true/false 表示是否移除成功
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
}
看完这两个 API,可能会产生一个疑问,如果 ArrayList 中存放的是 Integer 类型,调用 remove(1)
时移除的究竟是元素 1 还是下标为 1 的元素。其实很容易猜到,传入的是原始类型,会优先匹配参数是原始类型的方法,传入对象引用,优先匹配对象引用类型。下面这个小实验验证了这一点。
class ArrayListRemoveTest{
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(6,7,8));
list.remove(Integer.valueOf(1)); // 传入一个 Integer 实例,调用 remove(Object)
System.out.println(list); // 6,7,8
list.remove(1); // 传入原始类型,调用的是 remove(int)
System.out.println(list); // 6,8
}
}
removeAll 和 retainAll 几乎是一样的,都是调用了 batchRemove,通过一个标志表示移除的是 c 包含的原始还是 c 不包含的元素。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0; // r 表示遍历的当前元素,w 表示下一个元素存放的位置,因为会移除,所以后面的元素逐个往前挪
boolean modified = false;
try { // 放到 try 块中是为了在 contains 抛出异常时不会中断执行
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) { // 抛异常时会出现 r != size 的情况,这时直接将下标从 r 开始的元素往 w 位置复制
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) { // w == size 表示上面一系列骚操作没有移除一个元素。
// 这里表示如果有元素移除,则将数组从 w 开始设置为 null,从而让 GC 回收这些对象
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
removeIf 传入一个 Predict 函数,这个函数有一个与 ArrayList 中元素类型一样的参数,可以在 Predict 函数中编写判断移除元素的逻辑,函数返回真则移除。
这里使用了一个位集 removeSet 来标志要移除的元素,removeSet.get(i) 返回 true 表示第 i 个元素会被移除。这里在设置移除元素过程中使用到了前面提到的 modCount 变量,它先将 modCount 复制到 expectModCount,然后对元素逐个进行测试,同时不断判断 modCount 是否被修改过(本质是检查迭代 elementData 时,elementData 有没有被修改过,因为只要调用了变更 elementData 的方法,modCount 值就会发生改变),发现被修改了就抛出 ConcurrentModificationException。
这是一种 fail-fast 的设计思想,是用来探测 bug 的。如果在迭代的过程中被迭代的数据结构发生改变,将可能产生不可预知错误,与其让错误在将来某个不确定的时间产生,不如立即主动的抛出异常,避免潜在 bug。但这种探测并非百分之百有效,因为 ArrayList 是非线程安全的,如果两个线程同时对一个 ArrayList 对象进行操作,也可能出现探测不到 modCount 变化的情况。
public boolean removeIf(Predicate<? super E> filter) {
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); // 用一个位集来标志要移除的元素,先标志,后面再统一移除。
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++) { // 逐个移除,算法与上面 batchRemove 类似
i = removeSet.nextClearBit(i); // i 指向下一个不要移除的元素,即下一个 removeSet.get(i) 返回 false 的 i
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) { // 还是将 size 之后的元素置位 null,让 GC 能够回收这些对象
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
if (modCount != expectedModCount) { // 还是要检查,确保整个过程 ArrayList 没有被其它线程修改
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
clear 方法简单粗暴,直接将 elementData 数组所有元素置为了 null,让这些元素能够被 GC 回收。
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
查
方法 | 说明 |
---|---|
public E get(int index) | 获取索引为 index 的元素 |
public int indexOf(Object o) | 返回第一个与 o 相等的元素的索引,不存在则返回 -1,o 可以为 null |
public int lastIndexOf(Object o) | 返回最后一个与 o 相等的元素的索引,不存在则返回 -1,o 可以为 null |
public int size() | 返回 ArrayList 中元素的数量 |
public boolean isEmpty() | 判断是否有元素 |
public boolean contains(Object o) | 判断是否包含与 o 相等的元素,o 可以为 null |
public List |
返回下标范围为 [fromIndex, toIndex) 的元素,返回的是一个 SubList 对象,它是一个内部类 |
前面几个方法比较简单,其中 get 方法直接通过下标拿到元素,效率极高,RandomAccess 接口也说明了这一点。indexOf 和 lastIndex 都是通过遍历 elementData 数组来获取下标的。size 和 isEmpty 两个方法则是直接根据 size 变量的值来返回结果。contains 调用了 indexOf 方法得到结果。
subList 返回了当前 ArrayList 范围为 [fromIndex, toIndex) 的视图,视图是一个 SubList 对象,如果 fromIndex 与 toIndex 相等,则返回一个空的 List。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
SubList 是 ArrayList 的一个成员内部类。它通过直接访问 ArrayList 的 elementData 来达到操作数据的结果。也就意味者可以对返回的 SubList 视图进行读写,删除等操作,效果会作用到相应的 ArrayList 对象。但是有一个问题,在获得 SubList 对象开始到修改 SubList 对象期间,ArrayList 对象不能被修改,否则会抛出 IndexOutOfBoundsException,同样它是利用 modCount 来检查的。
操作 SubList 对象时,索引会映射到 ArrayList 的对象,其中 SubList 的索引 0 映射到 ArrayList 的索引 firstIndex。
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; // 将宿主对象的 modCount 复制到内部类对象的 modCount
}
......
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
private void checkForComodification() { // SubList 对象中所有涉及到变更数据结构的操作都会调用此方法
if (ArrayList.this.modCount != this.modCount) // 通过比较两个 modCount 的值检查 ArrayList 结构是否被修改
throw new ConcurrentModificationException();
}
如下代码,获取了一个 SubList 对象,然后在修改 SubList 对象之前修改 ArrayList 对象,再去修改 SubList 对象时抛出异常。
class SubListMod{
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
List<Integer> subList = list.subList(1,3);
System.out.println(subList); // 输出 [2,3]
list.add(4);
subList.add(5); // 抛出 ConcurrentModificationException
}
}
改
方法 | 说明 |
---|---|
E set(int index, E element) | 用 element 替换索引为 index 的元素,并返回被替换的元素 |
由于基于数组的存储结构,使用 set(int index, E element) 修改元素是十分高效的。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
迭代
索引
由于 ArrayList 支持高校随机访问,因此使用基于索引的方式迭代一个 ArrayList 是十分高效的。
for(int i=0; i<list.size(); i++){
process(list.get(i));
}
Iterator
ArrayList 间接实现了 Iterable 接口,因此必须实现 iterator() 方法,此方法将返回一个迭代器 Iterator。ArrayList 中的成员内部类 Itr 实现了 Iterator,调用 iterator() 方法将返回一个 Itr 对象。
public Iterator<E> iterator() {
return new Itr();
}
在使用 Itr 对 ArrayList 进行迭代的时候,不能直接调用 ArrayList 的成员方法对当前 ArrayList 对象进行修改,否则会抛出 ConcurrentModificationException,这里同样通过 modCount 机制来检查是否抛出异常。但是可以调用 remove() 方法移除迭代器游标刚刚越过的元素,也就是刚访问过的元素。不过不支持增加元素。需要注意的是,虽然可以调用 Itr 的 remove() 方法移除元素,但是不能同时有两个迭代器执行移除操作,否则仍然会抛出 ConcurrentModificationException。
while(iterator.hasNext()){
Element e = iterator.next();
iterator.remove(); // 将移除元素 e
}
private class Itr implements Iterator<E> {
int cursor; // 指向下一个应该返回的元素
int lastRet = -1; // 指向上一个刚刚返回(访问)过的元素,-1 表示没有访问过任何元素
int expectedModCount = modCount;
public boolean hasNext() { // 根据 cursor 是否移动到了 size 位置来判断是否还有元素返回
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification(); // 检查是否有其它线程修改了 elementData
int i = cursor;
if (i >= size) // true 表示所有的元素都迭代过了
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) // true 表示有其它线程修改了 elementData 的结构,因为正常情况下 i 不可能指到 elemtData.length 位置
throw new ConcurrentModificationException();
cursor = i + 1; // 每调用一次 next, cursor 往前移动一步
return (E) elementData[lastRet = i]; // 更新上一次访问的元素的索引
}
public void remove() { // 移除刚刚访问过的元素
if (lastRet < 0) // 如果没有访问过任何元素就直接调用 remove(),抛出 IllegalStateException
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); // 本质还是调用了 ArrayList 的 remove() 方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 只不过同步了一下 modCount 和 expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) { // 遍历迭代器中剩下的还未访问的元素,传入一个 Consumer
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) { // 在 consumer 中同样不能够调用 ArrayList 的 API 修改结构
consumer.accept((E) elementData[i++]);
}
// 在迭代结束之后再更新 cursor 和 lastRet 的值,而不是在循环体内去更新,为了减少堆写入流量。
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
上面的迭代器是标准的迭代器,可以通过增强的 for 语句去迭代 ArrayList 对象。
for(Element e : list){
process(e);
}
上面使用增强的 for 循环迭代 list 是一种语法糖,其本质为下面代码。这就解释了为什么增强的 for 循环体中不能修改被迭代的 ArrayList 对象了。
Element e;
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); ){
e = (Element)iterator.next();
process(e);
}
基于 ListIterator
List 接口提供了一个返回 ListIterator 抽象方法,ArrayList 中的成员内部内 ListItr 实现了它。相比于 Iterator,ListIterator 的功能更加强大。ListItr 实现了 ListIterator,继承了 Itr 来复用一些方法和成员变量。
ListIterator 不仅能够向前迭代(调用 next),而且能够向后回退(调用 previous)。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) { // index 表示从第几个元素开始进行迭代
super();
cursor = index; // 复用了父类中的 cursor,它指向下一个 next 应该返回的元素,previous 应该返回的元素在 cursor - 1 的位置
}
public boolean hasPrevious() { // 当 cursor == 0 时表示往前没有元素了
return cursor != 0;
}
public int nextIndex() { // 返回下一个元素的索引
return cursor;
}
public int previousIndex() { // 下一个应该返回的元素的前一个位置
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1; // i 指向 previous 应该返回的元素的位置
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 void set(E e) { // 用 e 替换刚刚返回过的元素,无论调用的是 next 还是 previous
if (lastRet < 0) // 如果没有调用过 next 和 previous,即 lastRet 还是初始状态,则抛出状态异常
throw new IllegalStateException();
checkForComodification();
try { // ListIterator 非线程安全,可能存在多个线程同时更新 lastRet 的情况,这时 lastRet 值可能为 -1,抛出 IndexOutOfBoundsException
// 而实际是由于并发修改 ArrayList 对象引起的,所以抛出 ConcurrentModificationException 可以更加准确地描述异常
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) { // 添加一个元素到 cursor 位置,也就是 next 即将返回的元素的位置,或者 previous 刚刚返回过的元素位置
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1; // 添加之后 lastRet 就被重置了,不能连续 listItr.set 了。因为 set 本身就是替换刚刚访问过的元素,调用了add可认为刚刚没有访问过元素
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
上面 3 个方法,记忆起来比较绕,其实可以将 cursor 想象成为一根在元素之间的棍子。调用 next 就往右翻越,调用 previous 就往左翻越。棍子在最左边时,不能再往左翻越,即不能再继续调用 previous;同理,在最右边时,不能继续往右翻越,即不能再调用 next。调用 listIterator.remove 和 listIterator.set 方法时,总是操作刚刚越过的元素,调用 listIterator.add 方法时,直接放在棍子右边即可。
操作 | 元素位置 |
---|---|
初始 | ` |
add('E') | `E |
next() | `EA |
remove() | `E |
add('E') | `EE |
next() | `EEB |
add('F') | `EEBF |
next() | `EEBFC |
previous() | `EEBF |
remove() | `EEBF |
previous() | `EEB |
set('A') | ` |
remove() | ` |
代码: |
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Arrays;
class ListIteratorTest{
public static void main(String[] args){
ArrayList<Character> list = new ArrayList<>(Arrays.asList('A', 'B', 'C', 'D'));
ListIterator<Character> it = list.listIterator(); System.out.println(list); // ABCD
it.add('E'); System.out.println(list); // EABCD
it.next();
it.remove(); System.out.println(list); // EBCD
it.add('E'); System.out.println(list); // EEBCD
it.next();
it.add('F'); System.out.println(list); // EEBFCD
it.next();
it.previous();
it.remove(); System.out.println(list); // EEBFD
it.previous();
it.set('A'); System.out.println(list); // EEBAD
it.remove(); System.out.println(list); // EEBD
}
}
小结
ArrayList 是基于动态数组的数据结构,适用于需要频繁进行查询的操作。由于其删除元素需要移动大量元素,新增可能导致扩容,因此它不适用于需要频繁进行新增删除的操作。
ArrayList 通过 modCount 机制来检查是否有某个线程修改一个正在被迭代的 ArrayList 对象,如果有,则立即抛出 ConcurrentModificationException,这是遵循了 fail-fast 原则;但是,因为 ArrayList 对象是非线程安全的,并发修改 ArrayList 也有可能不能被 modCount 机制检测到。意味着我们不能在非线程安全的环境下使用 ArrayList,否则可能出现不可预知且难以检测的错误。
ArrayList 支持 Iterator 和 ListIterator,在使用这两个迭代器的过程中不能调用 ArrayList 提供的方法修改 ArrayList 对象,但是可以通过迭代器提供的方法进行修改。