zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    ASPxGridView控件的基本属性
    未整理笔记
    ASPxGridView中Command列自定义按钮点击事件概要
    Cookie的使用
    ASPxGridView行的选中和行的焦点
    改变“骨感”,让OKR变得“丰满”
    高成就者的5个共同点
    项目的风险管理
    实际执行动作对OKR目标落地的重要性
    13种公认的高绩效习惯
  • 原文地址:https://www.cnblogs.com/phpstudy2015-6/p/10660457.html
Copyright © 2011-2022 走看看