ArrayList 源码分析

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 subList(int fromIndex, int toIndex) 返回下标范围为 [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 对象,但是可以通过迭代器提供的方法进行修改。

posted @ 2020-11-23 23:37  Robothy  阅读(226)  评论(0编辑  收藏  举报