• Java Iterator(迭代器)小笔记


    Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList和 HashSet 等集合。Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口。

    迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

     

    迭代器 it 的两个基本操作是 next 、hasNext 和 remove。

     

    Java中的Iterator功能比较简单,并且只能单向移动:

    使用方法iterator()要求容器返回一个Iterator。 注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

    调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。

    调用 it.hasNext() 用于检测集合中是否还有元素。

    调用 it.remove() 将迭代器返回的元素删除。

    Iterator 类位于 java.util 包中,使用前需要引入它,语法格式如下:

    import java.util.Iterator; // 引入 Iterator 类
    

    1.获取一个迭代器并循环集合元素

    集合想获取一个迭代器可以使用 iterator() 方法

    package com.joshua317;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    
    public class Main {
    
        public static void main(String[] args) {
            //创建集合
            ArrayList<String> names = new ArrayList<>();
            String[] namesArr = {"小明","二蛋","joshua317"};
            names.addAll(Arrays.asList(namesArr));
    
            System.out.println(names);
    
            //获取迭代器
            Iterator<String> iterator = names.iterator();
            //遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
            while (iterator.hasNext()) {
                String name = iterator.next();
                System.out.println(name);
            }
        }
    }
    
     

    2.删除集合里面的元素

    要删除集合中的元素可以使用 remove() 方法。

    package com.joshua317;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    
    public class Main {
    
        public static void main(String[] args) {
            //创建集合
            ArrayList<String> names = new ArrayList<>();
            String[] namesArr = {"小明","二蛋","joshua317"};
            names.addAll(Arrays.asList(namesArr));
    
            System.out.println(names);
    
            //获取迭代器
            Iterator<String> iterator = names.iterator();
            //遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
            while (iterator.hasNext()) {
                String name = iterator.next();
                //删除集合里面字符串为"joshua317"的元素
                if (name.equals("joshua317")) {
                    iterator.remove();
                }
            }
        }
    }
    
     

    3.扩展-迭代器的原理

    所有迭代器都最终实现接口Iterator,Iterator接口中包含三个基本方法,next(), hasNext(), remove(),其中对于List的遍历删除只能用Iterator的remove方法;JDK1.8中Iterator接口的源码如下:

    package java.util;
    
    import java.util.function.Consumer;
    
    public interface Iterator<E> {
    
        boolean hasNext();
    
        E next();
        
        default void remove() {
            throw new UnsupportedOperationException("remove");
        }
        
        default void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (hasNext())
                action.accept(next());
        }
    }
    

    接下来,我们基于ArrayList的Iterator的实现分析terator的原理(基于JDK1.8)

    在ArrayList类中有个方法iterator(),此方法将返回一个iterator的实现,这里可以看出实现类叫Itr,通过其它源码可知,此类是AarryList的内部类,即ArryList的Iterator实现在ArrayList内部;

    public Iterator<E> iterator() {
            return new Itr();
        }
    

    然后看下ArrayList中实现类Itr类的源码

    private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            Itr() {}
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();
                int i = cursor;
                if (i >= size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public void forEachRemaining(Consumer<? super E> consumer) {
                Objects.requireNonNull(consumer);
                final int size = ArrayList.this.size;
                int i = cursor;
                if (i >= size) {
                    return;
                }
                final Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length) {
                    throw new ConcurrentModificationException();
                }
                while (i != size && modCount == expectedModCount) {
                    consumer.accept((E) elementData[i++]);
                }
                // update once at end of iteration to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    

    从上面代码中可以看出,对于Iterator的实现中主要有几个变量cursor,lastRest, expectedModCount三个变量,其中cursor将记录下一个位置,lastRet记录当前位置,expectedModCount记录没有修改的List的版本号。

    问题抛出:List中在iterator遍历的时候,为什么不能随便添加和删除元素吗?

    在iterator遍历的时候抛出异常都是checkForComodification作的,根本原因是modCout和expectedModCount不相等,导致抛出异常。

    我们来看下ArrayList的add和remove方法

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    

    从代码中可以看出只要对ArrayList作了添加或删除操作都会增加modCount版本号,这样在迭代期间,会不断检查modCount和迭代器持有的expectedModCount两者是否相等,如果不相等就抛出异常了。

    关于modCount,API解释如下:
    
    The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
    
    也就是说,modCount记录修改此列表的次数:包括改变列表的结构,改变列表的大小,打乱列表的顺序等使正在进行迭代产生错误的结果。
    
    注意:仅仅设置元素的值并不是结构的修改
    
    我们知道的是ArrayList是线程不安全的,如果在使用迭代器的过程中有其他的线程修改了List就会抛出ConcurrentModificationException
    
    package com.joshua317;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    
    public class Main {
    
        public static void main(String[] args) {
            //创建集合
            ArrayList<String> names = new ArrayList<>();
            String[] namesArr = {"小明","二蛋","joshua317"};
            names.addAll(Arrays.asList(namesArr));
    
            //获取迭代器
            Iterator<String> iterator = names.iterator();
            //遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
            while (iterator.hasNext()) {
                String name = iterator.next();
                names.add("小红");
            }
        }
    }
    
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
    	at java.util.ArrayList$Itr.next(ArrayList.java:861)
    	at com.joshua317.Main.main(Main.java:19)
    
     

    虽然在迭代器迭代期间不能对ArrayList作任何增删操作,但是可以通过iterator的remove作删除操作,从之前的代码可以看出,在iterator的remove()中有一行代码,expectedModCount = modCount; 这个赋值操作保证了iterator的remove是可用性的。

    当然,iterator期间不能增删的根本原因是ArrayList遍历会不准,就像遍历数组的时候改变了数组的长度一样。

     

  • 相关阅读:
    k8s使用
    7月3日课堂笔记
    7月6日课堂笔记
    画倒三角形
    6月29日课堂笔记
    understand试用笔记一阅读VS2010项目
    Spring Boot 入门(九)使用RabbitMQ
    Spring Boot 入门(十一)使用Schedule
    Spring Boot 入门(十三)使用Elasticsearch
    学习MySQL
  • 原文地址:https://www.cnblogs.com/joshua317/p/15965623.html
Copyright © 2020-2023  润新知