zoukankan      html  css  js  c++  java
  • 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 对象,但是可以通过迭代器提供的方法进行修改。

  • 相关阅读:
    【Nginx+Tomcat】高性能负载均衡的Tomcat集群
    【JS-Excel】使用JS导出表格数据、附带解决科学计数法等问题
    【Util】日期工具类总结
    【SpringMVC】url映射传参
    【Linux+Windows】Linux,Windows打包发布到Tomcat并修改映射的ip地址
    【Spring】解决返回json乱码问题
    【API】高德地图API JS实现获取坐标和回显点标记
    ELK-Python(二)
    ELK-Python(一)
    zookeeper集群
  • 原文地址:https://www.cnblogs.com/robothy/p/13969448.html
Copyright © 2011-2022 走看看