首页 > 其他分享 >Iterator和LlistIterator迭代器的使用及底层原理:

Iterator和LlistIterator迭代器的使用及底层原理:

时间:2023-06-29 11:36:30浏览次数:50  
标签:结点 Iterator 迭代 list next add nextIndex LlistIterator

先来看下面的示例:

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接口,初始值为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);
        }
    }
}
/**
 * 打印结果:
 * 唐僧
 * 孙悟空
 * 沙僧
 * 小白龙
 */

标签:结点,Iterator,迭代,list,next,add,nextIndex,LlistIterator
From: https://blog.51cto.com/u_16154651/6580573

相关文章

  • iterator
       ......
  • maltab 利用不同方式(自编高斯赛德尔迭代函数,逆矩阵,左除(\)运算)求解线性方程组的速度
    参考:matlabhelp文档:mldivide实际测试比较,这里K_Tem为一个2398*2398的稀疏矩阵,Guass_Seidal是自己写的高斯赛德尔迭代函数 ......
  • 迭代器和生成器
    根据许多平台(例如GitHub),JavaScript是目前最流行的编程语言。然而,流行就等于是最先进或最受喜爱的语言吗?它缺少某些被认为是其他语言不可或缺的组成部分的结构,例如广泛的标准库、不变性和宏。但在我看来,有一个细节没有得到足够的重视——发电机。在本文中,我想解释迭代器和生成器的......
  • 迭代器模式
    迭代器模式这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式。思考问题:如何实现顺序访问且不知道集合底层表示?例子:迭代接口packageorg.kouhao.design.patterns.迭代模式;/***@authoradmin*/publicinterfaceIterato......
  • 深度学习/图像处理历史最全最细-网络、技巧、迭代-论文整理分享
        本资源整理了深度学习/图像处理技术发展过程中的所有模型、优化技巧、网络结构优化、迭代过程中所有经典论文,并进行了详细的分类,按重要程度进行了仔细的划分,对于想要了解深度学习模型迭代朋友来说非常值得参考。     本资源整理自网络,源地址:https://github.com/xw-hu/......
  • Python 迭代器和生成器
    Python迭代器和生成器1、迭代器迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于......
  • MongoDB批量导入Redis优化迭代笔记
    背景统计最近五天所有content信息的正文字节数(正文字段占用较多),然后根据这个大小,推送存在redis要配置多少的内存。统计方法1.在mongodb中查询db.content_.aggregate([{$match:{updatetime:{$gte:1686134400000,//对应日期"2023-06-07T00:00:00Z"的......
  • 强化学习从基础到进阶-常见问题和面试必知必答[2]:马尔科夫决策、贝尔曼方程、动态规划
    强化学习从基础到进阶-常见问题和面试必知必答[2]:马尔科夫决策、贝尔曼方程、动态规划、策略价值迭代1.马尔科夫决策核心词汇马尔可夫性质(Markovproperty,MP):如果某一个过程未来的状态与过去的状态无关,只由现在的状态决定,那么其具有马尔可夫性质。换句话说,一个状态的下一个状态......
  • 强化学习从基础到进阶-常见问题和面试必知必答[2]:马尔科夫决策、贝尔曼方程、动态规划
    强化学习从基础到进阶-常见问题和面试必知必答[2]:马尔科夫决策、贝尔曼方程、动态规划、策略价值迭代1.马尔科夫决策核心词汇马尔可夫性质(Markovproperty,MP):如果某一个过程未来的状态与过去的状态无关,只由现在的状态决定,那么其具有马尔可夫性质。换句话说,一个状态的下一个状态......
  • 代码随想录算法训练营第十二天| 递归遍历 (必须掌握)迭代遍历 统一迭代
    递归遍历重点:1,TreeNode的自定义2,val=0== val=NULL;代码:1voidpreRecursor(TreeNode*root,vector<int>&result)2{3if(root==NULL)4return;5result.push_back(root->val);6preRecursor(root->left,result);7......