迭代器详解
在使用集合的时候,如果想对集合进行删除,遍历等操作,那么我们就不得不使用到迭代器。那么在使用迭代器的时候会有一些注意点,本文将从如何使用入手,对迭代器以及并发修改异常的原理进行深入的阐述。
迭代器的作用:
可为集合或者数组进行迭代。
只要是实现了 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 进行判断是否相等。如果不等,就报并发修改异常。
|
|