ConcurrentModificationException异常之iterator和Iterable
本文将介绍iterator和Iterable的联系,以及由迭代器遍历对集合操作产生的问题
首先有必要说一下的是for-each也叫增强for循环(JDK1.5提出的)
,for-each其实是for循环的一个特殊简化版,专门用来操作数组和集合,其内部原理其实是迭代器
。
一、异常
- 下面我们来看两个抛出异常的例子
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();
}
}
- 未抛出异常的例子
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
- Collection和迭代器之间的联系
public interface Collection<E> extends Iterable<E> {
Iterator<E> iterator();
}
- map和迭代器之间的联系
对于Map集合我们之间看不到它与迭代器之间的关联,通过map.keySet()我们得值返回的是一个Set集合。
- 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);
}
}
- 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());
}
}
- 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接口,并包含其他的功能,比如:添加元素,替换元素,获取前一个和后一个元素的索引。