67 | 迭代器模式(下):如何设计实现一个支持“快照”功能的iterator?
王争
该思维导图由 AI 生成,仅供参考
上两节课,我们学习了迭代器模式的原理、实现,并且分析了在遍历集合的同时增删集合元素,产生不可预期结果的原因以及应对策略。
今天,我们再来看这样一个问题:如何实现一个支持“快照”功能的迭代器?这个问题算是对上一节课内容的延伸思考,为的是帮你加深对迭代器模式的理解,也是对你分析、解决问题的一种锻炼。你可以把它当作一个面试题或者练习题,在看我的讲解之前,先试一试自己能否顺利回答上来。
话不多说,让我们正式开始今天的学习吧!
问题描述
我们先来介绍一下问题的背景:如何实现一个支持“快照”功能的迭代器模式?
理解这个问题最关键的是理解“快照”两个字。所谓“快照”,指我们为容器创建迭代器的时候,相当于给容器拍了一张快照(Snapshot)。之后即便我们增删容器中的元素,快照中的元素并不会做相应的改动。而迭代器遍历的对象是快照而非容器,这样就避免了在使用迭代器遍历的过程中,增删容器中的元素,导致的不可预期的结果或者报错。
接下来,我举一个例子来解释一下上面这段话。具体的代码如下所示。容器 list 中初始存储了 3、8、2 三个元素。尽管在创建迭代器 iter1 之后,容器 list 删除了元素 3,只剩下 8、2 两个元素,但是,通过 iter1 遍历的对象是快照,而非容器 list 本身。所以,遍历的结果仍然是 3、8、2。同理,iter2、iter3 也是在各自的快照上遍历,输出的结果如代码中注释所示。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了如何设计实现一个支持“快照”功能的迭代器,解决了在遍历集合的同时增删集合元素可能产生不可预期结果或报错的问题。文章提出了两种解决方案,一种是在迭代器类中定义一个成员变量snapshot来存储快照,另一种是在容器中为每个元素保存添加时间戳和删除时间戳,并在迭代器中使用时间戳来实现快照功能。此外,文章还提出了解决随机访问问题的方法,即在ArrayList中存储两个数组,一个用于标记删除实现快照遍历,另一个用于支持随机访问。整体而言,本文为读者提供了丰富的技术知识和解决问题的思路,强调了通过主动思考和实践来锻炼分析问题、解决问题的能力,以及代码设计能力、编码能力的重要性。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》,新⼈⾸单¥98
《设计模式之美》,新⼈⾸单¥98
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(72)
- 最新
- 精选
- 万历十五年思考题:为迭代器创建虚引用,当迭代器被回收时,清空容器中相应元素。
作者回复: 嗯嗯 ������
2020-11-29340 - 桃花河逆流而上1.代码貌似跑得不对,只有justNext方法自增了cursorInAll,假设第一个元素没有被删除,那么总是cursorInAll总是0; 2.时间戳获取有点问题,连续操作获取时间戳很有可能都是一样的,应该将时间戳进行递增操作,防止相等
作者回复: 1. 我再看下 2. 可以取纳秒时间,也可以用版本号
2020-04-2632 - Mew151方案二中: public E get(int i) { if (i >= totalSize) { throw new IndexOutOfBoundsException(); } return (E)elements[i]; } 这个方法不需要判断第i个元素是不是已删除的吗?
作者回复: 要看这个函数的定义了,文章中对你的疑问(按照下标随机访问)也有点解释,你可以再看下
2020-08-11 - 嘉一发现一个问题,如果在SnapshotArrayIterator创建前添加了几个数据,那么会不会出现这几个数据的添加时间戳和SnapshotArrayIterator创建的时间戳是一样(因为计算机的时间戳最小是毫秒,而添加数据毕竟是非常快的操作,可能在不到一毫秒的时间就完成了)的导致这几个数据遍历不了?
作者回复: 可以使用纳秒时间或者版本号
2020-04-21 - LJK思考题感觉像是数据库的MVCC? - 容器中维护一个每个迭代器创建时间的列表 - 每次有迭代器创建时就在这个列表中加入自己的创建时间 - 迭代器迭代完成后将列表中对应时间点删除 - 清理容器时,对于容器中每个元素,如果addTime小于这个列表中的最小时间点就可以进行删除2020-04-061193
- Monday在阅读本节代码实现就想到了第二种方案存在类似思考题的问题 解决方案可以在合适的时候清理带删除标记的元素。本想使用数据库的多版本控制(MVCC)的方案,把所有的迭代器对象存起来,并添加创建时间。但是冒出一个新问题,数据库事务有commit来表示事务已完成,而迭代器的使用完成无法知晓,还在思考方案中……2020-04-06919
- smartjia感觉代码有两个小问题,若理解有误,请帮指正: 问题1. 重复删除同一个元素时,actualSize 应该保持不变。 以下是修改后的代码: @Override public void remove(E obj) { for (int i = 0; i < totalSize; ++i) { if (elements[i].equals(obj) && delTimestamps[i] == Long.MAX_VALUE) { // 防止重复删除 delTimestamps[i] = System.currentTimeMillis(); actualSize--; } } } 问题2: 遍历完一个元素后需要让 cursorInAll 自增,否则 cursorInAll 一直指向第一个待遍历的元素。同时hasNext() 恒为true, 也需要修改。 以下是修改后的代码: @Override public E next() { E currentItem = arrayList.get(cursorInAll); cursorInAll++; //自增,否则 cursorInAll 一直不变 justNext(); return currentItem; } @Override public boolean hasNext() { return this.leftCount >= 0; // 注意是>=, 而非> ( 修改后 leftCount >= 0 恒成立, 总是返回 true) 改为: return cursorInAll < arrayList.totalSize(); }2020-04-07915
- 辣么大课后思考题:类似数组动态扩容和缩容,删除元素时可以比较被删除元素的总数,在被删除元素总数 < 总数的 1/2 时, 进行resize数组,清空被删除的元素,回收空间。2020-04-06315
- 辉哥课堂讨论:可以创建一个object类型的常量,删除数组元素时,可以将被删除数组元素的引用指向该常量。Android中的SparseArray就是采用此方式实现的2020-04-0757
- 马以记录一个迭代变量,每迭代一次,计数加一,迭代完一次减一,当为0的时候就可以删除标记为delete的元素了2020-04-0627
收起评论