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

以脑图的形式来展示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() 则会删除后面的元素
 1         List<String> list = new LinkedList<>();
 2         list.add("aaa");
 3         list.add("bbb");
 4         list.add("ccc");
 5
 6         ListIterator<String> listIterator = list.listIterator();
 7
 8         //迭代器位置: add-1 | aaa bbb ccc
 9         listIterator.add("add-1");
10         // add-1 add-1 | aaa bbb ccc
11         listIterator.add("add-2");
12
13         // 返回: aaa
14         // add-1 add-1 aaa | bbb ccc
15         listIterator.next();
16         // add-1 add-1 aaa-set | bbb ccc
17         listIterator.set("aaa-set");
18         // bbb
19         // add-1 add-1 aaa-set bbb | ccc
20         listIterator.next();
21
22         // 返回: bbb
23         // add-1 add-1 aaa-set | bbb ccc
24         listIterator.previous();
25         // add-1 add-1 aaa-set | bbb-set ccc
26         listIterator.set("bbb-set");
27
28         // 删除 bbb-set
29         listIterator.remove();
30         listIterator.remove();
31
32         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 迭代器过程。

 1 // AbstractList.java
 2 // listIterator() 方法 LinkedList 类本身并没有重写,需要追溯到 AbstractList 抽象类
 3
 4     // 获取 ListIterator 迭代器
 5     public ListIterator<E> listIterator() {
 6         return listIterator(0);
 7     }
 8
 9     public ListIterator<E> listIterator(final int index) {
10         rangeCheckForAdd(index);    // 检查 index 范围是否超出
11
12         return new ListItr(index);  // 该抽象类也有实现 ListItr 类
13     }
14
15     private void rangeCheckForAdd(int index) {
16         if (index < 0 || index > size())
17             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
18     }
  1 // LinkedList.java
  2 // LinkedList 类重写了 listIterator(int index) 方法
  3
  4     public ListIterator<E> listIterator(int index) {
  5         checkPositionIndex(index);  // 同理 检查 index 范围;相关代码就不贴了
  6         return new ListItr(index);
  7     }
  8
  9
 10     private class ListItr implements ListIterator<E> {
 11         private Node<E> lastReturned;   // 上一次处理的节点
 12         private Node<E> next;   // 即将要处理的节点
 13         private int nextIndex;  // 即将要处理的节点的 index
 14         // modCount 表示集合和迭代器修改的次数;expectedModCount 表示当前迭代器对集合修改的次数
 15         private int expectedModCount = modCount;
 16
 17         ListItr(int index) {
 18             // assert isPositionIndex(index);
 19             next = (index == size) ? null : node(index);
 20             nextIndex = index;
 21         }
 22
 23         public boolean hasNext() {
 24             return nextIndex < size;
 25         }
 26
 27         /**
 28         * 处理对象:迭代器当前的 next 节点
 29         * 将处理目标储到 lastReturned 变量中
 30         * 然后将当前的 next.next 节点保存起来,用于下一次迭代处理
 31         * nextIndex 同时 +1
 32         * 返回 lastReturned.item 元素
 33         * 执行后:lastReturned 指向该次处理的节点;next、nextIndex 指向该次处理节点的后一个节点
 34         */
 35         public E next() {
 36             // 检查 modCount 与 expectedModCount 是否相等
 37             // 实际检查该链表是否被其他迭代器或者集合本身修改
 38             checkForComodification();
 39             // 判断是否存在 next 节点
 40             if (!hasNext())
 41                 throw new NoSuchElementException();
 42
 43             lastReturned = next;    // 将这次返回的 node 节点更新到迭代器中的 lastReturned 变量
 44             next = next.next;   // 将下一次需要处理 node 节点更新会 next 变量
 45             nextIndex++;    // 变量 nextIndex +1
 46             return lastReturned.item;   // 返回元素
 47         }
 48
 49         public boolean hasPrevious() {
 50             return nextIndex > 0;
 51         }
 52
 53         /**
 54         * 处理对象:迭代器当前的 next.prev 节点
 55         * 将处理目标储到 lastReturned 变量中
 56         * 然后将当前的 next.prev 节点保存起来,用于下一次迭代处理
 57         * nextIndex 同时 -1
 58         * 返回当前的 next.item 元素
 59         * 执行后:next、lastReturned、nextIndex 指向该次处理节点的前一个节点
 60         */
 61         public E previous() {
 62             checkForComodification();
 63             // 判断是否存在 prev 节点
 64             if (!hasPrevious())
 65                 throw new NoSuchElementException();
 66
 67             // 处理当前 next 的 prev 节点
 68             // 特殊情况:next = null 时,则它的 prev 节点为 last 节点
 69             lastReturned = next = (next == null) ? last : next.prev;
 70             nextIndex--;    // nextIndex -1
 71             return lastReturned.item;
 72         }
 73
 74         public int nextIndex() {
 75             return nextIndex;
 76         }
 77
 78         public int previousIndex() {
 79             return nextIndex - 1;
 80         }
 81
 82         /**
 83         * 处理对象:lastReturned
 84         * 删除 lastReturned 指向的节点,并置为 null
 85         * 同时保证 next 和 nextIndex 指向同一个节点
 86         */
 87         public void remove() {
 88             checkForComodification();   // 同理, 检查 modCount 与 expectedModCount 是否相等
 89             if (lastReturned == null)
 90                 throw new IllegalStateException();
 91
 92             Node<E> lastNext = lastReturned.next;  // 暂存 lastReturned 的 next 节点,用于恢复迭代状态
 93             unlink(lastReturned);   // 删除最后返回的节点    modCount++;
 94
 95             // 分迭代方向处理(因为删除一个节点后,需要恢复迭代状态:next 和 nextIndex 指向同一个节点)
 96             if (next == lastReturned)   // next 与 lastReturned 节点相同则表明最近一次迭代操作是 previous()
 97                 next = lastNext;        // 删除了原有 next 指向的节点,因此 nextIndex 相对指向的节点变为 next.next,需要更新 next 变量的指向
 98             else
 99                 nextIndex--;    // next() 迭代方向;删除了next前面的节点,因此next的相对位置发生变化,需要 nextIndex -1
100             lastReturned = null;
101             expectedModCount++;     // 同时 expectedModCount++
102         }
103
104         /**
105         * 处理对象:lastReturned
106         */
107         public void set(E e) {
108             if (lastReturned == null)
109                 throw new IllegalStateException();
110             checkForComodification();
111             lastReturned.item = e;
112         }
113
114         /**
115         * 分位置进行添加
116         */
117         public void add(E e) {
118             checkForComodification();
119             lastReturned = null;
120             if (next == null)
121                 linkLast(e);
122             else
123                 linkBefore(e, next);
124             nextIndex++;
125             expectedModCount++;
126         }
127
128         public void forEachRemaining(Consumer<? super E> action) {
129             Objects.requireNonNull(action);
130             while (modCount == expectedModCount && nextIndex < size) {
131                 action.accept(next.item);
132                 lastReturned = next;
133                 next = next.next;
134                 nextIndex++;
135             }
136             checkForComodification();
137         }
138
139         /**
140         * 检查 modCount 与 expectedModCount 是否相等,否则抛出错误
141         * ListIterator 迭代器进行增删操作时,都会同时对这两个变量 +1
142         * 目的:
143         * 使用 ListIterator 迭代器期间,LinkedList 对象有且只能当前这一个迭代器可以进行修改
144         * 避免 LinkedList 对象本身以及其他迭代器进行修改导致链表混乱
145         */
146         final void checkForComodification() {
147             if (modCount != expectedModCount)
148                 throw new ConcurrentModificationException();
149         }
150     }

小结

  总的来说 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

在 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

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

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

时间: 2024-11-05 21:47:27

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

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

上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它.内容包括: ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java集合系列之TreeMap源码分析

一.概述 TreeMap是基于红黑树实现的.由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法.另外TreeMap中不允许键对象是null. 1.什么是红黑树? 红黑树是一种特殊的二叉排序树,主要有以下几条基本性质: 每个节点都只能是红色或者黑色 根节点是黑色 每个叶子节点是黑色的 如果一个节点是红色的,则它的两个子节点都是黑色的 从任意一

Java集合系列之LinkedList源码分析

一.LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的. ps:这里有一个问题,就是关于实现LinkedList的数据结构是否为循环的双向链表,上网搜了有很多文章都说是循环的,并且有的文章中但是我看了源代码觉得应该不是循环的? 例如在删除列表尾部节点的代码: private E unlinkLast(Node<E> l) { final E element = l.item; final Node<E> pr

Java集合系列之ArrayList源码分析

一.ArrayList简介 ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加.如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能. ArrayList的用法和Vector向类似,但是Vect

Java集合系列之HashSet源码分析

一.HashSet简介 HashSet是Set接口典型实现,它按照Hash算法来存储集合中的元素,具有很好的存取和查找性能.主要具有以下特点: 不保证set的迭代顺序 HashSet不是同步的,如果多个线程同时访问一个HashSet,要通过代码来保证其同步 集合元素值可以是null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值确定对象在HashSet中的存储位置.在Hash集合中,不能同时存放两个相等的

Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概念,主要讲了AQS的排队区是怎样实现的,什么是独占模式和共享模式以及如何理解结点的等待状态.理解并掌握这些内容是后续阅读AQS源码的关键,所以建议读者先看完我的上一篇文章再回过头来看这篇就比较容易理解.在本篇中会介绍在独占模式下结点是怎样进入同步队列排队的,以及离开同步队列之前会进行哪些操作.AQS为在独占模

Java并发系列[5]----ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

Java集合:HashSet的源码分析

Java集合---HashSet的源码分析 一.  HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. 二.  HashSet的实现: 对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, Has

java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)

1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用到了cas相关的操作来保证线程安全的. 2.概述 ConcurrentHashMap这个类在java.lang.current包中,这个包中的类都是线程安全的.ConcurrentHashMap底层存储数据的结构与1.8的HashMap是一样的,都是数组+链表(或红黑树)的结构.在日常的开发中,我们