设计模式之美
王争
前 Google 工程师,《数据结构与算法之美》专栏作者
123425 人已学习
新⼈⾸单¥98
登录后,你可以任选6讲全文学习
课程目录
已完结/共 113 讲
设计模式与范式:行为型 (18讲)
设计模式之美
15
15
1.0x
00:00/00:00
登录|注册

65 | 迭代器模式(上):相比直接遍历集合数据,使用迭代器有哪些优势?

课堂讨论
重点回顾
优势
原理和实现
迭代器模式

该思维导图由 AI 生成,仅供参考

上一节课,我们学习了状态模式。状态模式是状态机的一种实现方法。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。
今天,我们学习另外一种行为型设计模式,迭代器模式。它用来遍历集合对象。不过,很多编程语言都将迭代器作为一个基础的类库,直接提供出来了。在平时开发中,特别是业务开发,我们直接使用即可,很少会自己去实现一个迭代器。不过,知其然知其所以然,弄懂原理能帮助我们更好的使用这些工具类,所以,我觉得还是有必要学习一下这个模式。
我们知道,大部分编程语言都提供了多种遍历集合的方式,比如 for 循环、foreach 循环、迭代器等。所以,今天我们除了讲解迭代器的原理和实现之外,还会重点讲一下,相对于其他遍历方式,利用迭代器来遍历集合的优势。
话不多说,让我们正式开始今天的学习吧!

迭代器模式的原理和实现

迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。
在开篇中我们讲到,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

迭代器模式是一种行为型设计模式,用于遍历集合对象。它将遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。文章介绍了迭代器模式的原理和实现,包括迭代器接口的定义和具体实现类的编写。通过示例演示了如何实现一个迭代器,以及如何在容器中定义iterator()方法来创建对应的迭代器。总结了迭代器的设计思路,包括定义基本方法、依赖注入传递容器对象、以及容器通过iterator()方法创建迭代器。文章通过实例和类图的方式,清晰地展示了迭代器模式的实现原理和应用。 迭代器模式的优势在于封装集合内部的复杂数据结构,简化遍历操作;将集合对象的遍历操作从集合类中拆分出来,让两者的职责更加单一;让添加新的遍历算法更加容易,更符合开闭原则。此外,基于接口而非实现编程,替换迭代器也变得更加容易。 总结内容包括迭代器模式的基本原理和实现,以及遍历集合的优势。读者可以通过本文快速了解迭代器模式的设计思路和应用场景,以及在遍历集合时使用迭代器的优势。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》
新⼈⾸单¥98
立即购买
登录 后留言

全部留言(49)

  • 最新
  • 精选
  • DFighting
    本质是因为迭代器从容器类抽象出来以后,一个容器的数据会被多个迭代器访问,这种类似数据库的并问题了,迭代器抽象出来可以让多个线程同时访问容器数据,但这也会带来一个问题:数据一致性。Java是通过在迭代器和容器中都维护了一个修改计数,remove的时候需要比较两个计数,不一致就会抛出异常

    作者回复: 嗯嗯������

    2020-11-21
    49
  • 咸鱼
    这一章看的挺迷惑的,我的理解是快照是提供一个scope内的一致性读,不受“他人”的影响,但是ArrayList又是在一个非并发环境下使用,这种快照好像是增加了实现复杂度。并发场景下的快照迭代,感觉好像看起来也没那么大需求。希望争哥看到可以解惑下

    作者回复: 快照本身就是我自己想出来的一个题目,确实没有需求啊

    2020-08-07
    2
    1
  • worthto
    在容器中定义一个Iterator这种方式,算不算是桥接设计模式?

    作者回复: 好像也不算是~

    2020-05-28
  • 守拙
    在 Java 中,如果在使用迭代器的同时删除容器中的元素,会导致迭代器报错,这是为什么呢?如何来解决这个问题呢? 通过阅读Java11 ArrayList#Iterator源码: iterator#remove()中, 调用外部类ArrayList#remove(), 通过下标移除元素. ArrayList内部维护modCount成员变量, 表示一次遍历中修改次数. Iterator通过remove()修改前, 会核对Iterator自己的exceptedModCount和ArrayList的modCount是否一致, 若不一致, 说明出现了并发问题, 会抛出异常. 若一致, 正常移除数据, 并更新modCount 结论: 1. 通过Iterator遍历集合时, 必须通过Iterator#remove()移除元素. ​ 2. 避免在并发情形修改集合, 或使用CopyOnWriteArrayList
    2020-04-01
    9
    81
  • 国奉
    漏掉状态模式实现电商的案例了
    2020-04-01
    39
  • pedro
    第一个问题,使用 for-each 或者 iterator 进行迭代删除 remove 时,容易导致 next() 检测的 modCount 不等于 expectedModCount 从而引发 ConcurrentModificationException。 在单线程下,推荐使用 next() 得到元素,然后直接调用 remove(),注意是无参的 remove; 多线程情况下还是使用并发容器吧😃
    2020-04-01
    2
    25
  • 课后题1: java中,容器中有int类型的变量ModCount来记录修改次数,每次新增或者删除容器内对象时都会给这个变量+1 在创建迭代器时会初始化一个变量expectedModCount(期待的操作次数) = ModCount,记录当前容器的增删操作次数,在使用迭代器时会不断检查expectedModCount是否等于ModCount(这个方式类似版本号机制,CAS解决ABA问题的方法),当他们不相等时就会抛异常 解决方法: 1.利用Arrays.copy方法,每次迭代前复制出一份副本,迭代这个副本(有可能会导致迭代的数据不一致) 2.如果是轮询的方式去执行,可以不用管这个异常,交给下一次轮询去处理 3.如果这个迭代操作最后产生的数据要返回给用户,也可以catch到这个异常时直接返回"数据正在处理中,请稍后再试" ps:如果这个场景有线程安全问题,建议用方法1,用副本来迭代 。 使用并发安全容器并不能解决所有的并发安全问题,因为线程安全可以分为绝对安全,相对安全,线程兼容和线程对立,绝对安全的成本太高,我们通常说的线程安全都是相对安全,即这个对象的单次操作时是线程安全的(举例:一次delete或者一次fori循环处理操作,分别是安全的,但是如果一条线程在做delete操作,另一条线程用fori循环处理,那么fori循环这条线程势必会出现异常),而我们常说的并发安全容器HashTable,ConcurrentHashMap都是相对安全 最后,没有最好的方案,只有最合适的方案,应该根据具体的场景选择合适的处理方式
    2020-04-01
    2
    23
  • Corner
    因为在迭代器中保存的游标和集合有一致性关系(大小,元素位置)。迭代器外部删除集合元素将导致其保存的游标位置与集合当前状态不一致。解决方法是由迭代器本身提供删除方法,这样可以感知到删除操作以便调整本身保存的游标。
    2020-04-01
    14
  • Ken张云忠
    1.在 Java 中,如果在使用迭代器的同时两次删除容器中的元素,会导致迭代器报错,这是为什么呢?如何来解决这个问题呢? java.util.AbstractList中名为modCount的filed用来记录这个集合被结构性修改的次数; 内部类迭代器java.util.ArrayList.Itr中会有个名为expectedModCount的field,用来记录当前集合被修改的次数,当删除元素时会modCount会加1,而expectedModCount却保持不变,当再继续遍历时会检查modCount与expectedModCount是否相等,如果不相等就会抛出异常,中止程序往下执行. 解决方式,当删除元素时将更新的modCount同步给expectedModCount. 2.除了编程语言中基础类库提供的针com.mysql.cj.jdbc.result.ResultSetImpl对集合对象的迭代器之外,实际上,迭代器还有其他的应用场景,比如 MySQL ResultSet 类提供的 first()、last()、previous() 等方法,也可以看作一种迭代器,你能分析一下它的代码实现吗? com.mysql.cj.jdbc.result.ResultSetFactory将数据集com.mysql.cj.protocol.ResultsetRows作为构造参数传递给迭代器com.mysql.cj.jdbc.result.ResultSetImpl, ResultSetImpl实现了接口java.sql.ResultSet中的first()、last()、previous() 等函数,当我们上层遍历集合时只需调用操作java.sql.ResultSet既可以了,而当我们想要更换成oracle的jdbc时只需替换实现的依赖包即可,上层应用代码无需任何改动,迭代器模式将数据集与遍历操作进行了解耦才使我们可以轻松替换底层jdbc实现,这也符合软件开发中倡导的开闭原则.
    2020-04-03
    9
  • 李小四
    设计模式_65: # 作业 1. 刚看了一下源码,Java容器会校验修改次数`modCount`,与预期不一致就会抛出异常,这个设计是合理的:因为在使用迭代器的同时删除元素,很可能会带来数据的错误,甚至导致程序的崩溃,及时地暴露错误是正确的做法。 如何解决:单线程中使用`iterator.remove()`方法删除,多线程中使用并发集合。 # 感想 最早使用迭代器,是因为for循环删除元素会导致错误,就像今天的问题1。 现在看来,迭代器更重要的作用是解耦,呼应前面的原则就是`开闭原则`、`单一职责原则`、`里氏替换原则`。。。
    2020-04-01
    7
收起评论
显示
设置
留言
49
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部