博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一行一行读Java源码——迭代器
阅读量:5924 次
发布时间:2019-06-19

本文共 5129 字,大约阅读时间需要 17 分钟。

迭代器

我们都知道,当我们需要删除List中元素时,必须使用迭代器来操作,为什么需要使用迭代器来进行remove操作,而不能在for循环中删除?那么迭代器又是什么呢?

迭代器接口

以下代码是一个基本的迭代器接口,声明了迭代器的基本方法,而我们常用的就是hasNext、next、remove

package java.util;import java.util.function.Consumer;public interface Iterator
{ boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } // Java 1.8加入,用于支持lambda表达式 default void forEachRemaining(Consumer
action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }}

每一种数据结构,其迭代器的实现必然存在差异,此处我以List为例。

List统称为线性表,而线性表又分为顺序表和链表。接下来,我们来看看AbstractList中是如何实现这两种类型迭代器的。(顺序表也就是常见的数组)

顺序表的迭代器实现——Itr

private class Itr implements Iterator
{ // 游标,指示将要访问的元素的位置 int cursor = 0; // 指示最后一次访问位置,如果是最后一次是删除操作,该值为-1 int lastRet = -1; // 同步校验位,主要用于remove时校验是否有多线程并行删除元素,此机制类似乐观锁 int expectedModCount = modCount; // 是否还有可访问元素 public boolean hasNext() { return cursor != size(); } // 校验顺序表是否被其它线程修改过,未修改才可访问,否则抛ConcurrentModificationException异常 // 返回游标指示元素 // 标记最近一次访问位置为游标位置 // 游标向前推进一位 public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } // 判断该位置元素是否已经删除过 // 并发访问校验 // 调用List的当前实例删除当前(最近访问)元素 // 游标回退一位 // 最近访问的元素被删除后,那最近访问的元素下标标记为-1 // 因为AbstractList.this.remove(lastRet)中的remove方法会修改modCount的值,所有expectedModCount也需要被重新赋值 public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } // 校验当前List的修改数modCount是否和迭代器中存储的一致,防止多线程并发访问 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

链表的迭代器实现——ListItr

// 链表的迭代器实现继承了顺序表迭代器,所以它拥有顺序表迭代器的所有功能private class ListItr extends Itr implements ListIterator
{ // 增加了指定位置的迭代器 ListItr(int index) { cursor = index; } // 是否有前向节点(元素) public boolean hasPrevious() { return cursor != 0; } // 访问前向节点 // 因为链表不支持随机访问,所以访问前向节点也是从首节点开始遍历到游标位置的前一个节点,时间复杂度为O(n),迭代器虽然提供了这样的操作,但是我们平时使用时需要注意,尽量减少对链表的这种操作 public E previous() { checkForComodification(); try { int i = cursor - 1; E previous = get(i); lastRet = cursor = i; return previous; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } // 修改游标指示元素值 public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.set(lastRet, e); expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } // 此处体现了链表的优势 // 在游标位置插入一个元素,时间复杂度为O(1),如果是顺序表,插入一个元素的时间复杂度为O(n) public void add(E e) { checkForComodification(); try { int i = cursor; AbstractList.this.add(i, e); lastRet = -1; cursor = i + 1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }

结论

1、迭代器封装了对List的操作,使得访问更安全、准确,增删元素不是简单地通过List实例remove或者add,还包含了并发校验等;

2、两种for循环都不能准确删除元素,如下方例子所示。

public static void main(String[] args) {    List
digits = new ArrayList<>(); digits.add(0); digits.add(1); digits.add(2); for (int i = 0; i < digits.size(); i++) { System.out.println("Size of list : " + digits.size()); digits.remove(i); } for (Integer integer : digits) { digits.remove(integer); }}

输出

Size of list : 3Size of list : 2Exception in thread "main" java.util.ConcurrentModificationException    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)    at java.util.ArrayList$Itr.next(ArrayList.java:851)    at collection.ListRemove.main(ListRemove.java:19)

转载地址:http://jvovx.baihongyu.com/

你可能感兴趣的文章
Linux系统启动流程详解
查看>>
通过源码解析 Node.js 中一个 HTTP 请求到响应的历程
查看>>
CodeIgniter的密码处理论
查看>>
Spring Cloud Config服务器
查看>>
测试人员必学的软件快速测试方法(二)
查看>>
Agora iOS SDK-快速入门
查看>>
引入间接隔离变化(三)
查看>>
统一沟通-技巧-4-让国内域名提供商“提供”SRV记录
查看>>
cocos2d-x 3.0事件机制及用户输入
查看>>
比亚迪速锐F3专用夏季座套 夏天坐垫 四季坐套
查看>>
程序员全国不同地区,微信(面试 招聘)群。
查看>>
【干货】界面控件DevExtreme视频教程大汇总!
查看>>
闭包 !if(){}.call()
查看>>
python MySQLdb安装和使用
查看>>
Java小细节
查看>>
poj - 1860 Currency Exchange
查看>>
chgrp命令
查看>>
Java集合框架GS Collections具体解释
查看>>
洛谷 P2486 BZOJ 2243 [SDOI2011]染色
查看>>
linux 笔记本的温度提示
查看>>