深入迭代器,了解并发修改异常

迭代器的一些基础知识

  1. hasNext() :此方法用来判断下一个元素,其实后面可以通过源码去理解
  2. next() :获取迭代器对象当前索引位置的元素并将索引下标移至下一个元素
  3. remove() :删除参数中指定元素

https://www.cnblogs.com/zhuyeshen/p/10956822.html

通过一个问题深入迭代器

遍历ArrrayList移除一个元素

fori遍历

正序遍历

下面代码会出现一些紧邻需要移除的重复元素没有被移除

import java.util.ArrayList;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<Character> list = new ArrayList<>();
        list.add('a');
        list.add('b');
        list.add('c');
        list.add('c');
        for (int i = 0; i <list.size() ; i++) {
            System.out.println("当前list大小:"+list.size());
            if('c'==list.get(i)){
                list.remove(i);
                System.out.println("移除发生了,此时list大小"+list.size());
            }
        }
        for (Character character : list) {
            System.out.println(character);
        }
    }
}
当前list大小:4
当前list大小:4
当前list大小:4
移除发生了,此时list大小3
a
b
c

Process finished with exit code 0

怎么改进呢? 可以在条件成立的时候让i–,从而保证i还在原来的位置

import java.util.ArrayList;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<Character> list = new ArrayList<>();
        list.add('a');
        list.add('b');
        list.add('c');
        list.add('c');
        for (int i = 0; i <list.size() ; i++) {
            System.out.println("当前list大小:"+list.size());
            if('c'==list.get(i)){
                list.remove(i);
                i--;
                System.out.println("移除发生了,此时list大小"+list.size());
            }
        }
        for (Character character : list) {
            System.out.println(character);
        }
    }
}
当前list大小:4
当前list大小:4
当前list大小:4
移除发生了,此时list大小3
当前list大小:3
移除发生了,此时list大小2
a
b

Process finished with exit code 0

倒序遍历删除

import java.util.ArrayList;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<Character> list = new ArrayList<>();
        list.add('a');
        list.add('b');
        list.add('c');
        list.add('c');
        for (int i = list.size()-1; i >=0; i--) {
            System.out.println("当前list大小:"+list.size());
            if('c'==list.get(i)){
                list.remove(i);
                System.out.println("移除发生了,此时list大小"+list.size());
            }
        }
        for (Character character : list) {
            System.out.println(character);
        }
    }
}
当前list大小:4
移除发生了,此时list大小3
当前list大小:3
移除发生了,此时list大小2
当前list大小:2
当前list大小:2
a
b

Process finished with exit code 0

forEach会出现异常

先让ArrayList中的泛型是引用的时候,当移除元素后面没有其他元素是不报错,但是会发现少移除了一个

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");
        System.out.println(list.get(3)==list.get(2)); //true
        for (String s : list) {
            if(s.equals("c")){
                boolean remove = list.remove(s);
                System.out.println("移除:"+remove);
            }
        }
        System.out.println(list);

    }
}


true
移除:true
[a, b, c]

发现此时结果不对,有个c没有移除,这时候看源码

//ArrayList中remove(Object o)源码
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true; //移除一个后就返回true
                }
        }
        return false;
    }

快速移除的方法

private void fastRemove(int index) {
    modCount++;
    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  把末尾那个元素置为空
}
//src代表原数组,srcPos原数组起始 dest 复制到的目的数组  destPos目的数组的起始位置   需要的复制长度
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

[下面总结参考链接][https://www.cnblogs.com/ielgnahz/articles/11344406.html]

1、foreach循环遍历对象

foreach循环遍历对象的时候底层是使用迭代器进行迭代的,即该对象必须直接或者间接的实现了Iterable接口,一般以able结尾代表某种能力,实现了iterable代表给予了实现类迭代的能力。

2、foreach循环遍历数组

foreach循环遍历数组的时候将其转型成为对每一个数组元素的循环引用

还是按照上面同样的方法来进行查看

迭代器删除

迭代器对应的集合中如果元素修改,那么会导致迭代器会更新,当你调用的是迭代器的remove方法的时候,迭代器会让集合中元素移除,迭代器更新,同时迭代器会让当前cursor减1,保证还能接着正确遍历

正确的使用方式:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if("c".equals(iterator.next())){
               
                iterator.remove();
            }
        }
        System.out.println(list);
    }
}
[a, b, d]

错误的使用方式

看下面这中情况结果虽然是对的,但是通过打断点调试我发现了当next()返回值是’c’的时候,迭代器指针(cursor)会往下走一步到达’d’所在的位置,当再调hasNext返回已经为null了.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if("c".equals(iterator.next())){

                list.remove("c");
            }
        }
        System.out.println(list);
    }
}
[a, b, d]

下面会出现删除元素没有删除干净

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");


        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if("c".equals(iterator.next())){
                System.out.println("进入这了");
                list.remove("c");
            }
        }
        System.out.println(list);
    }
}
进入这了
[a, b, c]

如果下面再加一行,那么就会报并发修改异常

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");


        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println("进");
            if("c".equals(iterator.next())){
                System.out.println("进入这了");
                list.remove("c");
            }
        }
        System.out.println(list);
    }
}
进
进
进
进入这了
进
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at IteratorTest.main(IteratorTest.java:18)

Process finished with exit code 1

当迭代器遍历到第一个c的时候,通过集合的方法移除元素,这时候迭代器会更新,但是迭代器的指针还是处于c所在位置的下一个(因为取出3后下移了一个),暂且用3位置来说吧,那么更新后迭代器2位置是元素d,但是在hasnetx返回true,然后调用next方法取出当前位置元素的时候就会报并发修改异常,在上面为什么没有报,是因为刚好hasNext返回的是false,没有调用里面的next方法.不会进里面,通过源码你会看见next方法里面会抛出并发修改异常

这里重点是调next怎么报的并发修改异常和怎么更新迭代器呢?下面来看源码

迭代器源码探究

通过下面这个报并发修改异常的代码来探究源码

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");


        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println("进");
            if("c".equals(iterator.next())){
                System.out.println("进入这了");
                list.remove("c");
            }
        }
        System.out.println(list);
    }
}

调用iterator(),走ArrayList中重写的方法

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

在这里插入图片描述

会去new Itr()这个Itr是实现Iterator的类(ArrayList中的内部类,也可以称为个帮助类)

紧接着我们看这个Itr类中的方法

ArrayList中Itr的源码

首先我们看一下它的几个成员变量:

cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出

lastRet:表示上一个访问的元素的索引

expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

modCount是AbstractList类中的一个成员变量

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;
import java.util.Objects;

/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return 下标对于下一个元素返回 起始为0
    int lastRet = -1; // index of last element returned; -1 if no such    下标对于最后一个元素返回,没有返回-1
    int expectedModCount = modCount;         //期望的数量

    Itr() {}

    public boolean hasNext() { //判断是不是有下一个,就是判断cursor是不是等于size(集合的大小)
        return cursor != size; //不等于的时候返回true
    }

    @SuppressWarnings("unchecked")
    public E next() {  //next方法
        checkForComodification(); //检查,就是调用这个方法的时候里面可能包并发修改异常
        int i = cursor;  //i为当前cursor的值
        if (i >= size) //i大于等于size说明
            throw new NoSuchElementException();  //包没有元素异常
        Object[] elementData = ArrayList.this.elementData; //得到这个集合的元素,同时迭代器每次通过这个去更新的迭代器中
        if (i >= elementData.length) //i超过这个长度了,那么会报并发修改异常,可能多线程操作导致的
            throw new ConcurrentModificationException();
        cursor = i + 1; //每次操作这个值会加1
        return (E) elementData[lastRet = i];  //返回的是没加1千的值
    }

    //为什么调用迭代器remove不会包并发修改异常呢
    public void remove() {
        if (lastRet < 0) //还没有next过一次直接调用的时候会抛出不合法状态异常
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet); //调用remove还是会让modCount加1了
            cursor = lastRet; //这个步骤相当于让这个cursor往后退了一下
            lastRet = -1; //同时更新她为-1,下一次如果再调迭代器remove会报异常的,除非前面有过next
            expectedModCount = modCount; //更新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();
    }


    //这个是上面测试代码抛异常的地方
    //当调用集合的remove方法的时候你会发现会修改moCount的值,这个时候会发现modCount和expectedModCount
    //不相等,那么会抛并发修改异常
    final void checkForComodification() {  //检查modCount和期望的修改次数是否一样
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

走完这波源码,看看解释你应该明白了迭代器的原理,直接调用集合的remove的时候你会发现modCount改了,但是迭代器中的没改,这样会在下次调用next时候进入 checkForComodification() 方法报并发修改异常. 如果你是通过迭代器去调用的remove方法,那么该方法中会更新cursor = lastRet;相当于cursor指针后退一步,因为你移除元素了后面元素来占据这个位置了,同时更新expectedModCount这样就不会导致下次next的时候报并发修改异常了

ArrayLIst中ListItr

继承了Itr
在这里插入图片描述

  1. 使用范围不同,interator可以使用在List、Set、Queue这些接口的子类中,而ListIterator只能用在List的子类中
  2. ListIterator有add方法,set方法,而iterator没有
  3. 二者都有hasNext和Next方法,而ListIterator有hasPrevious和PreviousIndex方法可以实现逆向遍历,Iterator没有。注意:ListIterator实现逆向遍历是调用ListIterator(int index)时必须传参数
  4. ListIterator可以获得当前指针的位置,而Iterator没有
  5. ListIterator有set方法,Iterator没有

参考链接1

参考链接2

参考链接3

参考链接4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值