源码解析- java集合迭代器fail-fast/ fail-safe
fail-fast 快速失败 fail-safe 安全失败
说的是在Iterator遍历的过程中,是不能够修改集合数据的,否则就会抛出ConcurrentModificationException。
看个demo
执行结果
看一下我们第35行抛了异常 ConcurrentModificationException
追一下看看 ArrayList的第850行
这个方法是通过迭代器获取下一个元素
checkForComodification(); //问题就在这了,获取之前先进行一次判断
就是这了,如果判断成立直接抛得ConcurrentModificationException异常
我们看一下modCount和 expectedModCount分别是啥东西
modCount是AbstractList的一个属性,
大概意思就是 当集合的结构发生改变或者size发生变化时,modCount会发生变化
看一下ArrayList的几个会改变集合结构的方法 remove() add()
执行前都会有modCount++操作,也就是说只要集合的结构发生改变,这个modCount就会自增1
再看一下expectedModCount,
结了,也就是初始创建迭代器的时候就会expectedModCount = modCount
也就是说 迭代器在遍历的过程中,每次调用next()方法都会重新校验一下,看看这个modCount变没变,也就是有没有人修改了集合的结构,如果有直接抛异常ConcurrentModificationException
思考一下为什么不允许迭代器遍历的过程中修改集合结构?
大概是因为这边在这遍历查着,那边让人改了 会产生很奇怪的结果吧。
证明一下猜想。首先应从hasNext() next()这两个方法入手, 如何判断下一个元素存在的
这是ArrayList内部的迭代器实现,看出两个重要的属性,cursor size
看注释也看得出来,cursor就是游标 是迭代器内部的属性 从0开始遍历,
size是集合整体的大小 是ArrayList的属性,
hasNext()方法判断的就是当前游标和size
next()方法会使得游标+1 然后会记录一下最后一次取出元素的位置 lastRet(也就是刚才游标的位置)
那就证明了,如果我们从外部修改了集合的结构,集合的size会发生变化 但是游标cursor不会改,这不就会有问题了吗
最后看一下迭代器自带的remove()方法
先判断下modCount //确保没人傻了吧唧的从外部修改过集合结构
执行remove; //删的是lastRet位置(也就是迭代器最新一次取出来的元素)
cursor = lastRet; //游标并没有+1 保持不变
lastRet = -1; //最新一次取出来的元素已经被干掉了,给-1
expectedModCount = modCount; //重新对一下expectedModCount
好家伙,不知不觉把迭代器源码就瞅了一遍
fail-safe
一般的讲 Java.util包下的集合类都是fail-fast的,因为使用迭代器遍历的时候增删元素不安全
那么有不安全就有安全的, java.util.cuncurrent并发包下的集合就很安全
看个demo
一模一样的操作,换了copyOnWriteArrayList这个集合就可以了,这是为啥呢? (程序员的病: 为啥不行呢? 为啥又行呢?)
iterator() 传入 元素数组,0
看出来了 new 的时候保存了一份当前集合元素快照,
即使迭代器遍历的过程中集合发生改变也跟我没关系了,我遍历的始终是迭代器保存的快照 over