A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

迭代器详解


​    在使用集合的时候,如果想对集合进行删除,遍历等操作,那么我们就不得不使用到迭代器。那么在使用迭代器的时候会有一些注意点,本文将从如何使用入手,对迭代器以及并发修改异常的原理进行深入的阐述。

迭代器的作用:

​    可为集合或者数组进行迭代。
​    只要是实现了 Iterable 接口,就表示该容器可迭代。
​    就可以使用迭代器和增强for

迭代器的基本用法:

    ArrayList<Integer> al = new ArrayList<>();
            al.add(1);
            al.add(2);
            al.add(3);
            al.add(4);
            al.add(5);
    Iterator<Integer> it = al.iterator();
            while(it.hasNext()){
                Integer next = it.next();
                System.out.println(next);
            }
方法解释:

```
其中使用了三个方法的作用如下:
    iterator()//集合中的方法创建迭代器对象。
    hasNext() //判断是否有可迭代的元素。不移动指针
    next()      //一:获取元素          //二: 将指针向后移动一位
```


​    **网上有很多资料会认为刚开始创建的时候是默认指向-1索引,别急,后面会从源码的角度进行剖析。**

迭代器底层源码详解:

```
public Iterator<E> iterator() {

    return new Itr();  //返回的是Itr的对象
                        //Itr是ArrayList的一个内部类
}

//内部类
private class Itr implements Iterator<E> {
    int cursor;      //刚刚在图上画的那个指针.int类型默认初始化值为0.
    int lastRet = -1;
    int expectedModCount = modCount; //赋值给迭代器的成员变量.把集合变化的次数告诉给了迭代器.
    //modCount 在创建ArrayList对象时候.父类中的.默认初始化值为0
    //当add的时候 modCount ++ ,当 remove 的时候  modCount ++
      //set 方法 不会对modCount 的值进行改变的.
    //modCount 记录了集合长度变化的次数.
    //modCount是跟并发修改异常相关的变量,下面会介绍。



    public boolean hasNext() {
        return cursor != size; //指针指向的索引跟集合的长度进行一个比较.
                               //可以理解成当前指向的索引是否有元素。
                               //因为当指针指向最大索引再往后移动一个的时候才为size的值。
    }

    public E next() {
        checkForComodification();//检查并发修改异常的方法
                //实际上就是将迭代器记录的集合长度变化的次数
                //与实际集合长度变化的次数进行了比较.
                //如果两个值不相等,就会报并发修改异常.

        int i = cursor; //第一次的时候 会把0赋值给局部变量i
        if (i >= size)  //如果指针大于等于集合的长度了,此时会报没有元素异常
            throw new NoSuchElementException();//即当前指向的索引没有元素
        Object[] elementData = ArrayList.this.elementData;//elementData 集合中存数据的那个数组.
                        //有了一个副本.记录了集合中存数据的那个数组的地址值.
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1; // 就相当于把游标(指针)往后移动了一格
        return (E) elementData[lastRet = i];//第一次的时候i = 0 相当于elementData[0].获取0索引上的元素
    }
```

并发修改异常产生的原因:

​     当使用迭代器遍历集合的时候,使用了"集合"中的 增加/删除 方法,导致并发修改异常

​    **"注意":**让集合的长度发生了变化。如果是利用集合的set方法进行了修改,是不会导致并发修改异常

​    **所以**:其中如果仅仅是删除,那么可以使用 迭代器Iterator 和 列表迭代器ListIterator,如果是需要添加,那么就只能使用ListIterator

并发修改异常的原理:   

    集合调用 iterator 实际上创建了一个内部类对象
    public Iterator<E> iterator() {
        return new Itr();
    }
    private class Itr implements Iterator<E> {//内部类实现了Iterator接口
   
    int expectedModCount = modCount;
​    在创建对象的时候,就迭代器进行显示初始化。
​    其中modCount 在集合进行 add remove的时候都会进行++。
​    modCount表示集合长度修改的次数。
​    在创建对象时,迭代器内部expectedModCount 也记录了这个值。

​    如果此时集合的长度再次进行修改,modCount会继续++。
​    但是不会重新赋值给迭代器的expectedModCount
​    两者不匹配,就会导致并发修改异常。

并发修改异常主要产生原因:

```
    并发修改异常主要是next方法导致。

    public E next() {
        checkForComodification();//expectedModCount 和 modCount进行比较
       int i = cursor;
        if (i >= size)    //如果指针大于等于长度
            throw new NoSuchElementException();//报没有这个元素异常
        Object[] elementData = ArrayList.this.elementData;//将集合拷贝一个副本elementData
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;//next每调用一次指针就往后移动一个。
        return (E) elementData[lastRet = i];//从副本中获取相应的值
    }

------
    final void checkForComodification() {
        if (modCount != expectedModCount)//如果两者不匹配就并发修改异常
            throw new ConcurrentModificationException();
    }

------

    //再用迭代器进行删除的时候:
        //1,不能创建完迭代器对象后直接删除
        //2,不能连续删除两次
    public void remove() {
        if (lastRet < 0)  //为了防止迭代器刚创建的时候就调用remove方法
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);//调用集合的方法进行删除
            cursor = lastRet;//对指针进行了一个复位
            lastRet = -1;//方法连续删除两次
            expectedModCount = modCount;//把集合变化的次数重新告诉迭代器.
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
```

综上所述:

​    并发修改异常,是next()方法导致的。因为集合每一次进行增/删的时候都对modCount进行了++操作。所以modCount记录的是集合长度更改了几次。创建迭代器对象的时候,对expectedModCount进行了显示初始化。在创建迭代器对象的时候,迭代器自身记录了集合长度更改了几次。

​    所以说,在迭代器进行迭代的时候,如果对集合的长度再次更改,那么modCount的值会再次++。但是expectedModCount不会被重新复制,记录的还是原先创建迭代器的时候的那个值。所以 当调用next方法的时候,会先对 expectedModCount 和modCount 进行判断是否相等。如果不等,就报并发修改异常。

1 个回复

倒序浏览
我来占层楼啊   
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马