Loading...

Java高级之LinkedList的ListIterator迭代器

先来看下面的示例:

public class Demo {     public static void main(String[] args) throws IOException {         List<String> list = new LinkedList<>();         list.add("唐僧");         list.add("孙悟空");         list.add("猪八戒");         list.add("沙僧");         list.add("小白龙");         ListIterator<String> iterator = list.listIterator();          System.out.println(iterator.next());         System.out.println(iterator.next());         System.out.println(iterator.next());         System.out.println(iterator.next());         System.out.println(iterator.next());         // System.out.println(iterator.next());// 会抛出NoSuchElementException异常     } } /**  * 打印结果:  * 唐僧  * 孙悟空  * 猪八戒  * 沙僧  * 小白龙  */

那么该迭代器的原理到底是怎样的?查看该类的源码:

    /**      * ListIterator<E>迭代器的实现类      */     private class ListItr implements ListIterator<E> {         // 全局变量,上一次执行 next() 或者 previos() 方法时的节点         private Node<E> lastReturned;         // 后继结点         private Node<E> next;         // 后继结点的索引         private int nextIndex;         // 将修改次数modCount赋给expectedModCount         // modCount是指实际修改次数         // expectedModCount是指期望修改次数         private int expectedModCount = modCount;          /**          * 带参构造器          * @param index 指定索引          */         ListItr(int index) {             // assert isPositionIndex(index);             // 对next和nextIndex进行赋值,所以next就是根据索引index查询出来的结点             next = (index == size) ? null : node(index);             nextIndex = index;         }          /**          * 判断是否还有下一个结点          * @return 如果还有后继结点则返回true,否则返回false          */         public boolean hasNext() {             // nextIndex表示当前结点的索引             // size表示元素的实际个数             // 如果nextIndex小于size则表示仍然还有后继结点,如果大于等于size那么表示要么是尾结点,要么索引越界了             return nextIndex < size;         }          // 取下一个元素         public E next() {             // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改             checkForComodification();             // 2. 判断是否有下一个元素,如果没有则抛出NoSuchElementException异常             if (!hasNext()) // 表示hashNext()为false才会执行                 throw new NoSuchElementException();              // 3. 保存next结点             lastReturned = next;             // 4. 迭代器指向下一个结点             next = next.next;             // 5. 索引加1             nextIndex++;             // 6. 返回旧next结点的内容             return lastReturned.item;         }          /**          * 判断是否有前驱结点          * @return 如果有前驱结点返回true,否则返回false          */         public boolean hasPrevious() {             // 即判断nextIndex是否大于0             return nextIndex > 0;         }          /**          * 获取前驱结点          * @return 返回前驱结点          */         public E previous() {             // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改             checkForComodification();             // 2. 判断是否有上一个元素,空表或只有一个元素都没有前驱结点,如果没有则抛出NoSuchElementException异常             if (!hasPrevious())                 throw new NoSuchElementException();              lastReturned = next = (next == null) ? last : next.prev;             nextIndex--;             return lastReturned.item;         }          /**          * 返回下一个结点的索引          * @return 下一个结点的索引值,从0开始的          */         public int nextIndex() {             // 直接返回nextIndex即可             return nextIndex;         }          /**          * 返回前驱结点的索引          * @return 前驱结点的索引          */         public int previousIndex() {             // 即nextIndex减去1的结果             return nextIndex - 1;         }          /**          * 使用迭代器进行迭代的时候不能进行调用list.remove()或list.add()删除修改元素,否则会抛出ConcurrentModificationException异常          * 所以如果要增加或删除元素需要使用迭代器Iterator内部的remove()和add()方法          */         public void remove() {             // 1. 检查modCount和expectedModCount是否相等,如果不相等,表示发生了修改             checkForComodification();             // 2. 判断lastReturned是否为null来判断迭代器的状态             if (lastReturned == null)                 throw new IllegalStateException();              // 3. 获取上一个结点的next结点的next结点,就是当前结点的后继结点             Node<E> lastNext = lastReturned.next;             // 4. 删除当前结点             unlink(lastReturned);              if (next == lastReturned)                 // 重新设置next结点,该指向被删除结点的下一个结点                 next = lastNext;             else                 nextIndex--;             // 将lastReturned置为null,便于回收             lastReturned = null;             // 同时expectedModCount修改次数加1             expectedModCount++;         }          /**          * 修改结点的值          * @param e 新值          */         public void set(E e) {             // 1. 检查迭代器的状态             if (lastReturned == null)                 throw new IllegalStateException();             // 2. 检查在迭代器进行迭代时是否修改了List集合             checkForComodification();             // 3. 直接修改当前结点的item属性值             lastReturned.item = e;         }          /**          * 添加结点          * @param e 待添加的结点内          */         public void add(E e) {             // 1. 检查在迭代时是否有修改List集合             checkForComodification();              // 2. 将lastReturned置为null             lastReturned = null;             // 3. 判断next是否为null             // 3.1 如果为null,表示next是尾结点,那么将结点添加在末尾即可             if (next == null)                 linkLast(e);             // 3.2 表示不为null,那么插入在next结点之前             else                 linkBefore(e, next);             // 4. 收尾处理             // 4.1 nextIndex需要加1             nextIndex++;             // 4.2 由于添加了元素,expectedModCount也需要加1             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的值是否相等,所以当你在调用了ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,而迭代器中的expectedModCount未同步,因此才会导致再次调用Iterator.next()方法时抛出异常。          */         final void checkForComodification() {             // 本质上判断modCount是否等于expectedModCount             if (modCount != expectedModCount)                 // 如果不相等表示在迭代时调用了list.add()或list.remove(),那么抛出此异常                 throw new ConcurrentModificationException();         }     }

其实我们最应该关注的是next()方法,查看下图

从上图可以知道在创建迭代器时,next和nextIndex已经有内容了,看获得ListIterator迭代器的listIterator()方法

其中调用了ListItr构造器,默认索引index为0

所以在调用next()方法时做了三件事:

  • 保存next结点,返回该结点的item内容
  • 将next结点指向下一个结点
  • nextIndex索引加1

注意上面Demo.java中的代码,当第六次调用next()时将抛出NoSuchElementException异常,从next()源码中我们可以查看它首先要判断是否还有下一个元素。

没有下一个元素则抛出该异常。

所以我们如果要遍历迭代器,应该如下:

while (iterator.hasNext()) {     System.out.println(iterator.next()); }

但在遍历时可能遇到一个问题,如果你想删除或增加元素怎么办,看下面的代码:

public class Demo {     public static void main(String[] args) throws IOException {         List<String> list = new LinkedList<>();         list.add("唐僧");         list.add("孙悟空");         list.add("猪八戒");         list.add("沙僧");         list.add("小白龙");          ListIterator<String> iterator = list.listIterator();         while (iterator.hasNext()) {             String next = iterator.next();             if(next.equals("孙悟空")){                 list.remove("孙悟空");             }         }     } }

那么运行就会报错

看抛出这个异常的源代码:

原来如果modCount与expectedModCount不相等就会抛出异常。

因为在你迭代之前,迭代器已经被通过list.itertor()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,那么Java就会给出异常。因为此时Iterator对象已经无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出ConcurrentModificationException异常)。

那么modCount与expectedModCount是怎么回事呢?

  • modCount是在AbstractList中定义的一个变量,表示当前集合的增删次数,初始值为0,而LinkedList间接继承自AbstractList类,所以也有该属性,在该变量在对List进行添加(list.add())或删除(list.remove())操作时进行加1处理,表示对集合进行了修改。
  • expectedModCount是在ListItr类中定义的一个变量,表示当前迭代器的增删次数,该类实现了ListIteratror<E>接口,初始值为modCount,在调用该迭代器内部的添加和删除方法时才变化,平时不变化。

在上面代码中我们在迭代器中调用了LinkedList类的add()或remove()方法,只更新了modCount值,而迭代器中的expectedModCount没有更新,所以会抛出异常。

但如果调用ListIterator内部的remove()或add()方法就不会抛出此异常,因为同时更新了expectedModCount和modCount的值,如下源码能够证明:

使用的目的为了实现快速失败机制(fail-fast),在Java集合中有很多集合都实现了快速失败机制

快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。

看下面的例子就是多线程产生快速失败:

public class Demo {     public static List<String> list = new LinkedList<>();      public static void main(String[] args) throws IOException {         list.add("唐僧");         list.add("孙悟空");         list.add("猪八戒");         list.add("沙僧");         list.add("小白龙");          new Thread(new Runnable() {             @Override             public void run() {                 ListIterator<String> iterator = list.listIterator();                 while (iterator.hasNext()) {                     String next = iterator.next();                     System.out.println(next);                 }             }         }).start();          new Thread(new Runnable() {             @Override             public void run() {                 list.remove("猪八戒");             }         }).start();     } }

所以无论是单线程还是多线程为了避免抛出ConcurrentModificationException异常,也为了能够在使用迭代器遍历集合时对集合中元素进行增删操作,可以使用迭代器内部的remove()或add()方法。

public class Demo {     public static List<String> list = new LinkedList<>();      public static void main(String[] args) throws IOException {         list.add("唐僧");         list.add("孙悟空");         list.add("猪八戒");         list.add("沙僧");         list.add("小白龙");          ListIterator<String> iterator = list.listIterator();         while (iterator.hasNext()) {             String next = iterator.next();             if (next.equals("猪八戒")) {                 iterator.remove();             }         }          for (String s : list) {             System.out.println(s);         }     } } /**  * 打印结果:  * 唐僧  * 孙悟空  * 沙僧  * 小白龙  */

参考连接: