Java高级之LinkedList的ListIterator迭代器

2023年5月2日13:07:35

先来看下面的示例:

public class Demo {
    public static void main(String[] args) throws IOException {
        List<String> list = new LinkedList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");
        list.add("小白龙");
        ListIterator<String> iterator = list.listIterator();

        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        // System.out.println(iterator.next());// 会抛出NoSuchElementException异常
    }
}
/**
 * 打印结果:
 * 唐僧
 * 孙悟空
 * 猪八戒
 * 沙僧
 * 小白龙
 */

那么该迭代器的原理到底是怎样的?查看该类的源码:

    /**
     * ListIterator<E>迭代器的实现类
     */
    private class ListItr implements ListIterator<E> {
        // 全局变量,上一次执行 next() 或者 previos() 方法时的节点
        private Node<E> lastReturned;
        // 后继结点
        private Node<E> next;
        // 后继结点的索引
        private int nextIndex;
        // 将修改次数modCount赋给expectedModCount
        // modCount是指实际修改次数
        // expectedModCount是指期望修改次数
        private int expectedModCount = modCount;

        /**
         * 带参构造器
         * @param index 指定索引
         */
        ListItr(int index) {
            // assert isPositionIndex(index);
            // 对next和nextIndex进行赋值,所以next就是根据索引index查询出来的结点
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        /**
         * 判断是否还有下一个结点
         * @return 如果还有后继结点则返回true,否则返回false
         */
        public boolean hasNext() {
            // nextIndex表示当前结点的索引
            // size表示元素的实际个数
            // 如果nextIndex小于size则表示仍然还有后继结点,如果大于等于size那么表示要么是尾结点,要么索引越界了
            return nextIndex < size;
        }

        // 取下一个元素
        public E next() {
            // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
            checkForComodification();
            // 2. 判断是否有下一个元素,如果没有则抛出NoSuchElementException异常
            if (!hasNext()) // 表示hashNext()为false才会执行
                throw new NoSuchElementException();

            // 3. 保存next结点
            lastReturned = next;
            // 4. 迭代器指向下一个结点
            next = next.next;
            // 5. 索引加1
            nextIndex++;
            // 6. 返回旧next结点的内容
            return lastReturned.item;
        }

        /**
         * 判断是否有前驱结点
         * @return 如果有前驱结点返回true,否则返回false
         */
        public boolean hasPrevious() {
            // 即判断nextIndex是否大于0
            return nextIndex > 0;
        }

        /**
         * 获取前驱结点
         * @return 返回前驱结点
         */
        public E previous() {
            // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
            checkForComodification();
            // 2. 判断是否有上一个元素,空表或只有一个元素都没有前驱结点,如果没有则抛出NoSuchElementException异常
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        /**
         * 返回下一个结点的索引
         * @return 下一个结点的索引值,从0开始的
         */
        public int nextIndex() {
            // 直接返回nextIndex即可
            return nextIndex;
        }

        /**
         * 返回前驱结点的索引
         * @return 前驱结点的索引
         */
        public int previousIndex() {
            // 即nextIndex减去1的结果
            return nextIndex - 1;
        }

        /**
         * 使用迭代器进行迭代的时候不能进行调用list.remove()或list.add()删除修改元素,否则会抛出ConcurrentModificationException异常
         * 所以如果要增加或删除元素需要使用迭代器Iterator内部的remove()和add()方法
         */
        public void remove() {
            // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改
            checkForComodification();
            // 2. 判断lastReturned是否为null来判断迭代器的状态
            if (lastReturned == null)
                throw new IllegalStateException();

            // 3. 获取上一个结点的next结点的next结点,就是当前结点的后继结点
            Node<E> lastNext = lastReturned.next;
            // 4. 删除当前结点
            unlink(lastReturned);

            if (next == lastReturned)
                // 重新设置next结点,该指向被删除结点的下一个结点
                next = lastNext;
            else
                nextIndex--;
            // 将lastReturned置为null,便于回收
            lastReturned = null;
            // 同时expectedModCount修改次数加1
            expectedModCount++;
        }

        /**
         * 修改结点的值
         * @param e 新值
         */
        public void set(E e) {
            // 1. 检查迭代器的状态
            if (lastReturned == null)
                throw new IllegalStateException();
            // 2. 检查在迭代器进行迭代时是否修改了List集合
            checkForComodification();
            // 3. 直接修改当前结点的item属性值
            lastReturned.item = e;
        }

        /**
         * 添加结点
         * @param e 待添加的结点内
         */
        public void add(E e) {
            // 1. 检查在迭代时是否有修改List集合
            checkForComodification();

            // 2. 将lastReturned置为null
            lastReturned = null;
            // 3. 判断next是否为null
            // 3.1 如果为null,表示next是尾结点,那么将结点添加在末尾即可
            if (next == null)
                linkLast(e);
            // 3.2 表示不为null,那么插入在next结点之前
            else
                linkBefore(e, next);
            // 4. 收尾处理
            // 4.1 nextIndex需要加1
            nextIndex++;
            // 4.2 由于添加了元素,expectedModCount也需要加1
            expectedModCount++;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }

        /**
         * 验证modCount的值和expectedModCount的值是否相等,所以当你在调用了ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,而迭代器中的expectedModCount未同步,因此才会导致再次调用Iterator.next()方法时抛出异常。
         */
        final void checkForComodification() {
            // 本质上判断modCount是否等于expectedModCount
            if (modCount != expectedModCount)
                // 如果不相等表示在迭代时调用了list.add()或list.remove(),那么抛出此异常
                throw new ConcurrentModificationException();
        }
    }

其实我们最应该关注的是next()方法,查看下图

从上图可以知道在创建迭代器时,next和nextIndex已经有内容了,看获得ListIterator迭代器的listIterator()方法

其中调用了ListItr构造器,默认索引index为0

所以在调用next()方法时做了三件事:

  • 保存next结点,返回该结点的item内容
  • 将next结点指向下一个结点
  • nextIndex索引加1

注意上面Demo.java中的代码,当第六次调用next()时将抛出NoSuchElementException异常,从next()源码中我们可以查看它首先要判断是否还有下一个元素。

没有下一个元素则抛出该异常。

所以我们如果要遍历迭代器,应该如下:

while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

但在遍历时可能遇到一个问题,如果你想删除或增加元素怎么办,看下面的代码:

public class Demo {
    public static void main(String[] args) throws IOException {
        List<String> list = new LinkedList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");
        list.add("小白龙");

        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if(next.equals("孙悟空")){
                list.remove("孙悟空");
            }
        }
    }
}

那么运行就会报错

看抛出这个异常的源代码:

原来如果modCount与expectedModCount不相等就会抛出异常。

因为在你迭代之前,迭代器已经被通过list.itertor()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,那么Java就会给出异常。因为此时Iterator对象已经无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出ConcurrentModificationException异常)。

那么modCount与expectedModCount是怎么回事呢?

  • modCount是在AbstractList中定义的一个变量,表示当前集合的增删次数,初始值为0,而LinkedList间接继承自AbstractList类,所以也有该属性,在该变量在对List进行添加(list.add())或删除(list.remove())操作时进行加1处理,表示对集合进行了修改。
  • expectedModCount是在ListItr类中定义的一个变量,表示当前迭代器的增删次数,该类实现了ListIteratror<E>接口,初始值为modCount,在调用该迭代器内部的添加和删除方法时才变化,平时不变化。

在上面代码中我们在迭代器中调用了LinkedList类的add()或remove()方法,只更新了modCount值,而迭代器中的expectedModCount没有更新,所以会抛出异常。

但如果调用ListIterator内部的remove()或add()方法就不会抛出此异常,因为同时更新了expectedModCount和modCount的值,如下源码能够证明:

使用的目的为了实现快速失败机制(fail-fast),在Java集合中有很多集合都实现了快速失败机制

快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。

看下面的例子就是多线程产生快速失败:

public class Demo {
    public static List<String> list = new LinkedList<>();

    public static void main(String[] args) throws IOException {
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");
        list.add("小白龙");

        new Thread(new Runnable() {
            @Override
            public void run() {
                ListIterator<String> iterator = list.listIterator();
                while (iterator.hasNext()) {
                    String next = iterator.next();
                    System.out.println(next);
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                list.remove("猪八戒");
            }
        }).start();
    }
}

所以无论是单线程还是多线程为了避免抛出ConcurrentModificationException异常,也为了能够在使用迭代器遍历集合时对集合中元素进行增删操作,可以使用迭代器内部的remove()或add()方法。

public class Demo {
    public static List<String> list = new LinkedList<>();

    public static void main(String[] args) throws IOException {
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");
        list.add("小白龙");

        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next.equals("猪八戒")) {
                iterator.remove();
            }
        }

        for (String s : list) {
            System.out.println(s);
        }
    }
}
/**
 * 打印结果:
 * 唐僧
 * 孙悟空
 * 沙僧
 * 小白龙
 */

参考连接:

  • 作者:二木成林
  • 原文链接:https://blog.csdn.net/cnds123321/article/details/114120155
    更新时间:2023年5月2日13:07:35 ,共 6540 字。