Java 集合系列(四)—— ListIterator 源码分析

时间:2021-11-10 21:18:44

以脑图的形式来展示Java集合知识,让零碎知识点形成体系

Iterator 对比

  Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素。
  Iterator 包含四个方法,分别是:next()、hasNext()、remove()、forEachRemaining(Consumer<? super E> action)

  Collection 接口继承 java.lang.Iterable,因此所有 Collection 实现类都拥有 Iterator 迭代能力。
  逆向思考,Iterable 面向众多的 Collection 类型实现类,定义的方法就不可能太定制化,因此 Iterator 定义的功能比较简单。
  仅有如上所列出来的四种方法,并且该迭代器只能够单向移动。

  由于 List 类型的 Collection 是一个有序集合,对于拥有双向迭代是很有意义的。
  ListIterator 接口则在继承 Iterator 接口的基础上定义了:add(E newElement)、set(E newElement)、hasPrevious()、previous()、nextIndex()、previousIndex() 等方法,使得 ListIterator 迭代能力增强,能够进行双向迭代、迭代过程中可以进行增删改操作。

现象与问题

  1. add() 方法在迭代器位置前面添加一个新元素
  2. next() 与 previous() 返回越过的对象
  3. set() 方法替换的是 next() 和 previous() 方法返回的上一个元素
  4. next() 后,再 remove() 则删除前面的元素;previous() 则会删除后面的元素
         List<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc"); ListIterator<String> listIterator = list.listIterator(); //迭代器位置: add-1 | aaa bbb ccc
listIterator.add("add-1");
// add-1 add-1 | aaa bbb ccc
listIterator.add("add-2"); // 返回: aaa
// add-1 add-1 aaa | bbb ccc
listIterator.next();
// add-1 add-1 aaa-set | bbb ccc
listIterator.set("aaa-set");
// bbb
// add-1 add-1 aaa-set bbb | ccc
listIterator.next(); // 返回: bbb
// add-1 add-1 aaa-set | bbb ccc
listIterator.previous();
// add-1 add-1 aaa-set | bbb-set ccc
listIterator.set("bbb-set"); // 删除 bbb-set
listIterator.remove();
listIterator.remove(); System.out.println(list);

很多书本都有给出这样子的结论:

  • 链表有 n 个元素,则有 n+1 个位置可以添加新元素;

  • add() 方法只依赖迭代器的+位置;remove() 和 set() 方法依赖于迭代器的状态(此时迭代的方向);

  • 连续两个 remove() 会出错,remove() 前应先执行 next() 或 previous()。

迭代同时修改问题:

  一个迭代器指向另一个迭代器刚刚删除的元素,则现在这个迭代器就变成无效的了(节点删除被回收;即使没被回收,该节点的前后引用也被重置为null)。
链表迭代器有能够检测到这种修改的功能,当发现集合被修改了,将会抛出一个 ConcurrentModificationException 异常

  为什么出现上面的这些现象与问题呢,我们还是从源码中寻找答案吧

源码分析

  有多个集合类根据自己的特点实现了 ListIterator 接口,其实现都大同小异,这里我们主要分析 LinkedList 中所实现的 ListIterator。

  首先我们来分析 LinkedList 的 listIterator() 和 listIterator(int index) 方法获取 ListIterator 迭代器过程。

 // AbstractList.java
// listIterator() 方法 LinkedList 类本身并没有重写,需要追溯到 AbstractList 抽象类 // 获取 ListIterator 迭代器
public ListIterator<E> listIterator() {
return listIterator(0);
} public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index); // 检查 index 范围是否超出 return new ListItr(index); // 该抽象类也有实现 ListItr 类
} private void rangeCheckForAdd(int index) {
if (index < 0 || index > size())
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
 // LinkedList.java
// LinkedList 类重写了 listIterator(int index) 方法 public ListIterator<E> listIterator(int index) {
checkPositionIndex(index); // 同理 检查 index 范围;相关代码就不贴了
return new ListItr(index);
} private class ListItr implements ListIterator<E> {
private Node<E> lastReturned; // 上一次处理的节点
private Node<E> next; // 即将要处理的节点
private int nextIndex; // 即将要处理的节点的 index
// modCount 表示集合和迭代器修改的次数;expectedModCount 表示当前迭代器对集合修改的次数
private int expectedModCount = modCount; ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
} public boolean hasNext() {
return nextIndex < size;
} /**
* 处理对象:迭代器当前的 next 节点
* 将处理目标储到 lastReturned 变量中
* 然后将当前的 next.next 节点保存起来,用于下一次迭代处理
* nextIndex 同时 +1
* 返回 lastReturned.item 元素
* 执行后:lastReturned 指向该次处理的节点;next、nextIndex 指向该次处理节点的后一个节点
*/
public E next() {
// 检查 modCount 与 expectedModCount 是否相等
// 实际检查该链表是否被其他迭代器或者集合本身修改
checkForComodification();
// 判断是否存在 next 节点
if (!hasNext())
throw new NoSuchElementException(); lastReturned = next; // 将这次返回的 node 节点更新到迭代器中的 lastReturned 变量
next = next.next; // 将下一次需要处理 node 节点更新会 next 变量
nextIndex++; // 变量 nextIndex +1
return lastReturned.item; // 返回元素
} public boolean hasPrevious() {
return nextIndex > 0;
} /**
* 处理对象:迭代器当前的 next.prev 节点
* 将处理目标储到 lastReturned 变量中
* 然后将当前的 next.prev 节点保存起来,用于下一次迭代处理
* nextIndex 同时 -1
* 返回当前的 next.item 元素
* 执行后:next、lastReturned、nextIndex 指向该次处理节点的前一个节点
*/
public E previous() {
checkForComodification();
// 判断是否存在 prev 节点
if (!hasPrevious())
throw new NoSuchElementException(); // 处理当前 next 的 prev 节点
// 特殊情况:next = null 时,则它的 prev 节点为 last 节点
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--; // nextIndex -1
return lastReturned.item;
} public int nextIndex() {
return nextIndex;
} public int previousIndex() {
return nextIndex - 1;
} /**
* 处理对象:lastReturned
* 删除 lastReturned 指向的节点,并置为 null
* 同时保证 next 和 nextIndex 指向同一个节点
*/
public void remove() {
checkForComodification(); // 同理, 检查 modCount 与 expectedModCount 是否相等
if (lastReturned == null)
throw new IllegalStateException(); Node<E> lastNext = lastReturned.next; // 暂存 lastReturned 的 next 节点,用于恢复迭代状态
unlink(lastReturned); // 删除最后返回的节点 modCount++; // 分迭代方向处理(因为删除一个节点后,需要恢复迭代状态:next 和 nextIndex 指向同一个节点)
if (next == lastReturned) // next 与 lastReturned 节点相同则表明最近一次迭代操作是 previous()
next = lastNext; // 删除了原有 next 指向的节点,因此 nextIndex 相对指向的节点变为 next.next,需要更新 next 变量的指向
else
nextIndex--; // next() 迭代方向;删除了next前面的节点,因此next的相对位置发生变化,需要 nextIndex -1
lastReturned = null;
expectedModCount++; // 同时 expectedModCount++
} /**
* 处理对象:lastReturned
*/
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
} /**
* 分位置进行添加
*/
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
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 是否相等,否则抛出错误
* ListIterator 迭代器进行增删操作时,都会同时对这两个变量 +1
* 目的:
* 使用 ListIterator 迭代器期间,LinkedList 对象有且只能当前这一个迭代器可以进行修改
* 避免 LinkedList 对象本身以及其他迭代器进行修改导致链表混乱
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

小结

  总的来说 ListIterator 是记录 List 位置的一个对象,它主要的成员变量是 lastReturned、next、nextIndex 以及 expectedModCount。

  1. next() 处理的是 next 节点,返回 next.item

  2. previous() 处理的是 next.prev 节点 返回 next.prev.item

  3. remove() 处理的是 lastReturned 节点,并置为null,但要注意的是,删除节点后的 next 与 nextIndex 需分情况处理。

  4. set() 处理的是 lastReturned 节点,lastReturned.item = e

  5. add() 添加,并将 lastReturned 置为null

  这就很好地解释上面所提到的一些现象与问题了。
  典型的就是连续两个 remove() 会报错,那是因为第一个 reomve() 之后 lastReturned 被置为null;第二个 remove() 处理的对象是null,因此炮锤 IllegalStateException

知识脑图

From Java Core Knowledge Tree

Java 集合系列(四)—— ListIterator 源码分析

在 github 上建了一个 repository ,Java Core Knowledge Tree,各位看官若是喜欢请给个star,以示鼓励,谢谢。
https://github.com/suifeng412/JCKTree

(以上是自己的一些见解,若有不足或者错误的地方请各位指出)

作者:那一叶随风   http://www.cnblogs.com/phpstudy2015-6/

原文地址: https://www.cnblogs.com/phpstudy2015-6/p/10660457.html

声明:本博客文章为原创,只代表本人在工作学习中某一时间内总结的观点或结论。转载时请在文章页面明显位置给出原文链接

Java 集合系列(四)—— ListIterator 源码分析的更多相关文章

  1. Java集合系列&lbrack;4&rsqb;----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  2. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  3. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  4. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

  5. Java集合系列&lbrack;3&rsqb;----HashMap源码分析

    前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...

  6. Java集合系列&lbrack;1&rsqb;----ArrayList源码分析

    本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...

  7. Java集合系列&lbrack;2&rsqb;----LinkedList源码分析

    上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点.本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于 ...

  8. java多线程系列&lpar;九&rpar;---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  9. Java并发系列&lbrack;2&rsqb;----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  10. Java并发系列&lbrack;3&rsqb;----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

随机推荐

  1. 升级Ubuntu 16&period;04 LTS后 DSL拨号上网&lpar;ppp&rpar;连接自动断开解决办法

    原本在Ubuntu 15.10用拨号上网没有问题,但升级了16.04 LTS后发现原来的DSL连接不上了.主要表现为: 1.在NetworkManager里面选择DSL Connection能够尝试拨 ...

  2. hdu - 2102 A计划 &lpar;简单bfs&rpar;

    http://acm.hdu.edu.cn/showproblem.php?pid=2102 题目还是不难,注意起点一定是(0,0,0),然后到达P点时间<=t都可以. 用一个3维字符数组存储图 ...

  3. 婚庆手机APP

    这是一个信息化的时代,即将步入婚姻殿堂的新人们,你们是否希望有这样一款手机软件能伴随你从结婚到婚后一路的历程呢?比如说请帖通过手机客户端来将结婚的时间地点流程共享给您的亲朋好友,将您的婚纱照.随拍.写 ...

  4. android通过代码获取华为手机的EMUI系统版本号

    因为app中用到华为推送,但是华为推送在不同版本上是存在不同问题的,需要单独来处理. 那么最基本的问题是要获取EMUI系统的版本号. 上网翻了很多博客帖子,基本上是在获取root权限下去读取/syst ...

  5. Hystrix-异常处理

    异常的传播和捕获 传播:在HystrixCommand实现的run()方法中跑出异常时,除了HystrixBadRequestException之外,其他异常均会被Hystrix认为命令执行失败并处罚 ...

  6. Windows 2008 如何启用 TLS1&period;1 1&period;2

    1.下载工具:下载>>> 2.在服务器运行本软件,点击“Bes”按钮,然后必须确保有TLS 1.2被选中,SSL2 SSL3可以不用选择,然后点击Apply按钮,这时系统提醒您重启, ...

  7. Python的快排应有的样子

    快排算法 ​ 简单来说就是定一个位置然后,然后把比它小的数放左边,比他大的数放右边,这显然是一个递归的定义,根据这个思路很容易可以写出快排的代码 ​ 快排是我学ACM路上第一个让我记住的代码,印象很深 ...

  8. 数据特征分析:3&period;统计分析 &amp&semi; 帕累托分析

    1.统计分析 统计指标对定量数据进行统计描述,常从集中趋势和离中趋势两个方面进行分析 集中趋势度量 / 离中趋势度量 One.集中趋势度量 指一组数据向某一中心靠拢的倾向,核心在于寻找数据的代表值或中 ...

  9. ArrayList源码分析和实例应用

    1.ArrayList介绍 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcces ...

  10. openlayer3之高级标注实现

    首先看实现效果: 实现要点: 1)树形标注实现 2)复杂标注样式定义 3)效率优化 1.树形标注实现 树形标注采用字体符号来实现,包括以下几个步骤 1)载入字体 2)设置标注值与字体对照关系 3)设置 ...