Android源码分析—Iterator

1.Iterator概述

在jdk中Iterator接口定义了标准化访问集合中对象的方法,用于对Collection中元素进行迭代,避免了向使用者暴露集合内部结构。这种方法已被抽象为迭代器模式:提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。通过迭代器模式可以实现容器类与外部类的功能分离,简化容器接口,方便了调用者以通用的方法去遍历容器内元素。下面我们先来介绍下迭代器的接口定义。

JDK源码剖析之Iterator

Iterator接口定义了一个返回Iterator对象的方法

JDK源码剖析之Iterator

Collection接口实现了Iterable接口,故所有实现了Collection接口的容器类(集合)都可以通过对象名.iterator()的方法获取该collection的iterator(迭代器)对象

在Map中同样提供了通过Collection获取迭代器的方法,以便对键值对的值域进行操作。

例如:

Collection<Object>values=hashMap.values();

Iterator<Object> itr=values.iterator();

Iterator接口作为Iterable接口的返回值,定义了如下三个方法:

JDK源码剖析之Iterator

boolean hasNext():判断游标右边是否还有元素

E next(): 返回游标右边的元素并将游标移至下一个位置

void remove():删除游标左边的元素

2.Iterator在各个集合中的实现2.1ListIterator

ListIterator是list集合对Iterator接口的增强版实现。除了Iterator的三个基本方法外,还增加了双向移动遍历,以及在迭代过程中添加修改元素的方法。在初始化ListIterator时,还可以提供一个index参数,即可实现从任何位置对元素的迭代。

2.2ArrayList

JDK源码剖析之Iterator

在ArrayList中iterator方法返回了一个Itr对象,是一个实现了Iterator接口的内部类

private class Itr implements Iterator<E>{

int cursor = 0;

int lastRet = -1;

int expectedModCount= modCount;

}

在Itr内部定义了三个int型的变量:cursor、lastRet、expectedModCount。其中cursor表示下一个元素的索引位置,初始值为0,即第一个元素。lastRet表示上一个元素的索引位置,所以lastRet的值永远比cursor值少1.在内部类实例化时,会将modCount的值赋给expetedModCount,modCount用来表示list被结构性修改的次数。所谓结构性修改是指,改变了list的size,或者在迭代过程中产生了不正确的结果。

JDK源码剖析之Iterator

hasNext()方法通过判断游标值是否等于集合大小来判断是否还有下一个迭代的元素

JDK源码剖析之Iterator

next()方法则通过游标值获取数组对应值,即迭代返回的下一个值,并将游标移至下一位。方法开始时,首先调用checkForComodification方法,检查集合在迭代过程中是否被修改。然后判断游标值是否大于等于数组大小,是的话抛出异常。接下来获取当前数组,再次判断游标是否大于等于数组大小,是的话抛出同样抛出ConcurrentModificationException异常。

JDK源码剖析之Iterator

remove()方法调用ArrayList的remove()方法删除lastRet位置元素,并修改expectedModCount。首先会检查lastRet的值。而从上面的next()方法结束时,都会将lastRet值赋值为前一元素下标,只有在执行完add和remove方法后会将值赋为-1,所以在增删集合元素时,对同一个元素多次执行remove或add操作,会引发IllegalStateExcepion。

2.3ListItr

JDK源码剖析之Iterator

在ArrayList中的内部类ListItr,实现了比ListIterator更加丰富的迭代功能。

JDK源码剖析之Iterator

previous()方法返回游标左边位置对应数组元素,并修改游标值

JDK源码剖析之Iterator

set()方法将lastRet位置对应的数组值修改为新的元素

JDK源码剖析之Iterator

add()方法用于添加元素,通过将expectedModCount同步,避免抛出ConcurrentModifException异常。

2.4关于ConcurrentModificationException

ConcurrentModificationException作为集合遍历时的常见异常,经常出现在iterator调用remove()或hasNext()方法时用到的checkForComodification ()方法里

JDK源码剖析之Iterator

该方法用于判断集合的修改次数是否合法,即判断遍历过程中是否被修改过。若modCount不等于expecModCount,则抛出ConcurrentModificationException

下面以ArrayList为例,说明其产生的原因。

JDK源码剖析之Iterator

JDK源码剖析之Iterator

JDK源码剖析之Iterator

JDK源码剖析之Iterator

从上面的案例可以看到,在迭代过程中调用ArrayList.remove()方法删除元素时,程序会抛出ConcurrentModificationException。而通过迭代器的remove()方法则不会抛出异常,并正常将元素删除。

通过查看Arraylist的remove()方法,可以看到在方法中修改了modCount,modCount用于记录集合被修改的次数,而迭代器中的expectedModCount仍保持不变,当集合的修改次数和迭代器的修改次数不一致时,抛出ConcurrentModificationException。而通过迭代器在迭代过程中修改集合时,会将两者保持一致,避免了异常的抛出。因此在对集合遍历时,只能通过迭代器对集合进行修改。

除了单线程操作外,当多个线程同时对集合进行结构性改变时,例如一个线程通过Iterator遍历集合中元素,另一个线程在某个时候修改了集合的结构,也有可能抛出异常。这种错误检查机制被称为fail-fast快速失败。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。快速失败机制保证了错误在迭代之前及时抛出,避免了更大程度的异常。想要避免fail-fast,有两种解决方案:

1)在所有可能改变modCount的方法上加锁,或使用Collections.synchronizedList加同步锁

2)使用CopyOnWriteArrayList代替ArrayList。CopyOnWrite容器为写时复制容器,以其add方法为例,当添加元素时,不直接往容器里加,而是将当前容器复制,往新的容器中添加,完成后将引用指向新的容器。从而实现对容器多并发的读取。

JDK源码剖析之Iterator

在CopyOnWriteArrayList的iterator的hasNext()方法中,可以看到其并未调用checkForComodification方法,自然也就不会引发快速失败机制

2.5LinkedList

LinkedList底层数据结构是基于双向循环链表的,通过节点存储当前节点信息以及前一个和后一个节点的引用。在LinkedList的Iterator同样是由一个内部类ListItr继承了ListIterator,由于LinkedList结构的不同,内部类实现也有所区别,包括四个属性值

JDK源码剖析之Iterator

2.6HashIterator

HashMap是基于哈希表的 Map 接口的实现,HashMap的底层主要是基于数组加链表来实现的。在HashIterator中保存了当前访问和Entry和下一个Entry。在初始化时,先找到一个不为空的Entry作为next()返回的值。

JDK源码剖析之Iterator

JDK源码剖析之Iterator

hasNext()方法通过判断next指向的Entry是否为空判断是否还有未访问的元素。nextEntry方法返回下一个不为空的Entry。若当前链表已访问完成,则去寻找下个数组中不为iy空的Entry

JDK源码剖析之Iterator

在HashIterator中并没有实现next方法,而是由继承类来实现。

2.7Enumeration(HashTable)

HashTable与HashMap类似,都是基于链表+数组实现的基于键值对的集合,不同的是HashTable不允许插入null作为键,并且HashTable是线程安全的,而HashMap不是线程安全的。在HashTable中同样可以通过iterator方法实现元素的迭代,不同的是HashTable中还实现了Enumeration方法,通过该接口中的hasMoreElements()和nextElement()方法来实现对下一个元素的访问,其他方法与HashMap类似。

JDK源码剖析之Iterator

2.8HashSet

HashSet是基于 HashMap 实现的,其底层采用HashMap来保存所有元素,当添加元素时,将该元素作为Key插入HashMap中(value值和Key相同),保证了集合的不重复。在调用HashIterator方法时,调用hashmap的keySet.iterator()方法返回hashmap的key,即该set的value。

JDK源码剖析之Iterator

LinkedHashSet是继承于HashSet的子类,其Iterator方法与HashSet类似。

发布了449 篇原创文章 · 获赞 183 · 访问量 71万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览