ConcurrentModificationException异常之iterator和Iterable

ConcurrentModificationException异常之iterator和Iterable

本文将介绍iterator和Iterable的联系,以及由迭代器遍历对集合操作产生的问题

  首先有必要说一下的是for-each也叫增强for循环(JDK1.5提出的),for-each其实是for循环的一个特殊简化版,专门用来操作数组和集合,其内部原理其实是迭代器

一、异常

  1. 下面我们来看两个抛出异常的例子
       Map<Integer,String> map=new HashMap<>();
        map.put(1,"java");
        map.put(2,"go");
        map.put(3,"python");
        Iterator<Integer> iterator1=map.keySet().iterator();
        while (iterator1.hasNext()){
            Integer key=iterator1.next();//在此位置抛出ConcurrentModificationException
            System.out.println("Key:"+key+" value:"+map.get(key));
            map.remove(key);//此处换成 iterator.remove();
        }
        ArrayList<String> array = new ArrayList<>();
        array.add("a");
        array.add("b");
        array.add("v");
        array.add("d");
        for (String str : array) { //在此位置抛出ConcurrentModificationException
            if("a".equals(str)){
                array.remove(str); //此处可换成 iterator.remove();
            }
        }
  1. 未抛出异常的例子
        ArrayList<String> array = new ArrayList<>();
        array.add("a");
        array.add("b");
        array.add("c");
        for (String str : array) {
            if("b".equals(str)){ //如果改成“c”,"a"呢?
                array.remove(str);
            }
        }

二、Iterable和Iterator

  1. Collection和迭代器之间的联系
        public interface Collection<E> extends Iterable<E> {
                    Iterator<E> iterator();
        }
  1. map和迭代器之间的联系
      对于Map集合我们之间看不到它与迭代器之间的关联,通过map.keySet()我们得值返回的是一个Set集合。
  2. Iterable源码解析
        package java.lang;
        import java.util.Iterator;
        public interface Iterable
        {
            //返回T类型的迭代器
            Iterator<T> iterator();
            //jdk1.8新增的,用于循环输出,对内部元素进行遍历,并对元素进行指定的操作
            default void forEach(Consumer<? super T> action) {
                Objects.requireNonNull(action);
                for (T t : this) {   //增强for循环遍历
                    action.accept(t);
                }
            }
            // jdk1.8新增的,提供一个可以并行遍历元素的迭代器,以适应现在cpu多核时代并行遍历的需求
            default Spliterator<T> spliterator() {
                return Spliterators.spliteratorUnknownSize(iterator(), 0);
            }
        }
  1. Iterator源码解析
        package java.util;
        public interface Iterator
        {
            //迭代相关的三个方法
            public abstract boolean hasNext();
            public abstract Object next();
            public abstract void remove();
            //Jdk1.8新增,使用Lambda表达式来遍历集合元素
            default void forEachRemaining(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                while (hasNext())   //迭代器遍历
                    action.accept(next());
            }
        }
  1. ArrayList类中与迭代器有关的源码
        public class ArrayList<E> extends AbstractList<E>
                implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
            public Iterator<E> iterator() {
                return new ArrayList.Itr();
            }

            private class Itr implements Iterator<E> {
                int cursor;       // 返回下一个元素的下标
                int lastRet = -1; // 返回最后元素的下标,-1表示没有
                int expectedModCount = modCount;//期望修改的次数为默认的修改次数
                //是否存在下一个元素
                public boolean hasNext() {
                    return cursor != size;
                }
                //next操作
                @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];
                }
                //remove操作
                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();
                    }
                }
                //forEachRemaining操作
                @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();
                }
            }
        }

  至此我们应该了解到集合和迭代器的关系了,下面开始分析异常出现的原因以及我们埋下的伏笔.
  通过上述源码我们可以看到,抛出异常信息的位置在checkForComodification()方法中,Iterator中任何对集合的操作都会进入到检查并发操作异常方法中,而抛出此异常的条件是modCount != expectedModCount
   对于上述未抛出异常的代码块,是因为没有走next()方法,直接在hashNext()方法中退出了,此时cursor=2,size=2;

三、总结

  • 使用普通for循环和迭代器本身携带的remove可以正常操作数据
  • Iterable接口是专门创建新的迭代器的,Iterator接口是一个专门设计迭代器的
  • 更重要的一点是:每一次调用Iterable的Iterator()方法,都会返回一个从头开始的Iterator对象,各个Iterator对象之间不会相互干扰,这样保证了可以同时对一个数据结构进行多个遍历。这是因为每个循环都是用了独立的迭代器Iterator对象。
  • Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常

四、补充一点

  ListIterator源码

public interface ListIterator<E> extends Iterator<E> {
	boolean hasNext();    //以正向遍历列表时,如果列表迭代器有多个元素,则返回 true
	E next();			//返回列表中的下一个元素。
	boolean hasPrevious();//如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
	E previous();//返回列表中的前一个元素。
	int nextIndex();//返回对 next 的后续调用所返回元素的索引。
	int previousIndex();//  返回对 previous 的后续调用所返回元素的索引。
	void remove();//  从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
	void set(E e);//用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
	void add(E e);//将指定的元素插入列表(可选操作)。
}
        ArrayList<String> array = new ArrayList<>();
        array.add("a");
        array.add("b");
        array.add("c");
        System.out.println("------------------------");
        ListIterator<String> listIterator=array.listIterator();
        while (listIterator.hasNext()){
            System.out.println("元素的索引:"+listIterator.nextIndex()+"   内容"+listIterator.next());
            if(listIterator.nextIndex()==1){
                listIterator.add("d");
            }else {
                listIterator.set("f");
            }
        }
        System.out.println("------------------------");
        while (listIterator.hasPrevious()){
            System.out.println("元素的索引:"+listIterator.previousIndex()+"   内容"+listIterator.previous());
        }
Iterator和ListIterator的区别?
  • Iterator可以以遍历Set和List集合,但是ListIterator只能用来遍历List
  • Iterator对集合只能向前遍历,ListIterator既可以向前也可以向后
  • ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  • ListLiterator实现了Iterator接口,并包含其他的功能,比如:添加元素,替换元素,获取前一个和后一个元素的索引。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值