博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重学Java集合类(五)—— 集合类的遍历
阅读量:4224 次
发布时间:2019-05-26

本文共 3002 字,大约阅读时间需要 10 分钟。

前言

在实际开发过程中,我们经常会遇到遍历集合类的情况。但是其内部实现又是如何的呢?使用时需要注意的地方呢?本文一一道来。

常用遍历方式

Java集合类包含了List接口、Map接口和Set接口,分别看一下三者的便利方式。

  • List接口的遍历方式
for (int i = 0; i < list.size(); i++) {       //doSomething     }     Iterator
it = list.iterator(); while (it.hasNext()) { //doSomething } for (String s2 : list) { //doSomething }
  • Map接口的遍历方式
for (Integer in : map.keySet()) {          //doSomething      }      Iterator
> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry
entry = it.next(); //doSomething } for (Map.Entry
entry : map.entrySet()) { //doSomething }
  • Set接口的遍历方式
Iterator
it = set.iterator(); while (it.hasNext()) { String value = it.next(); //doSomething } for(String s: set){ //doSomething }

总结一下,集合类的遍历方式包括迭代器和for循环。

ConcurrentModificationException

在对集合迭代的过程中对集合进行一些修改的操作, 比如说add,remove之类的操作, 搞不好就会抛出ConcurrentModificationException, 这一点在API文档上也有说的。在迭代时只可以用迭代器进行删除。

我们以ArrayList为例,当我们使用迭代器遍历的时候,会频繁调用hasNext和next方法,其代码如下:

public boolean hasNext() {            return cursor != size;        }        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];        }

而next方法调用了checkForComodification方法,看一下内部实现:

final void checkForComodification() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();        }

继续跟踪modCount和expectedModCount两个变量,在Itr类的成员变量里对expectedModCount初始化的赋值:

int expectedModCount = modCount;

而modCount是AbstractList中的一个protected的变量, 在对集合增删的操作中均对modCount做了修改, 记录的是操作次数。通过判断遍历操作前后modCount的值是否相等,继而判断是否抛出异常。

查看ArrayList源码可以看到add和remove方法都会修改modCount的值,HashMap的也含有一个名为modCount的内部变量,处理逻辑跟ArrayList类似,不再赘述。因此不合适的使用上述方法均可能产生ConcurrentModificationException。

fail-fast和fast-safe

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

在fail-safe机制下,任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException。但是fail-safe机制也有如下两个问题:1、需要复制集合,产生大量的无效对象,开销大;2、无法保证读取的数据是目前原始数据结构中的数据。

fail-fast解决办法

  • 方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

  • 方案二:使用CopyOnWriteArrayList来替换ArrayList。

CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 该类产生的开销比较大,但是在两种情况下,它非常适合使用:

  • 1,在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时;
  • 2:当遍历操作的数量大大超过可变操作的数量时;

遇到这两种情况使用CopyOnWriteArrayList来替代ArrayList再适合不过了。

总结

本文以常用集合类的遍历方式为起始,讲述了通过迭代器遍历可能引发的问题,并引出了内部的fail-fast机制,最后给出此机制的解决办法,希望能对读者有所启发。

转载地址:http://tsgmi.baihongyu.com/

你可能感兴趣的文章
一种实用的联网汽车无线攻击方法及车载安全协议
查看>>
光靠欺骗检测是不够的:对抗多目标跟踪的攻击
查看>>
基于微区块链的V2X地理动态入侵检测
查看>>
面向V2C场景的ADAS数字孪生模型构建方法
查看>>
Comma2k19数据集使用
查看>>
面向自动驾驶车辆验证的抽象仿真场景生成
查看>>
一种应用于GPS反欺骗的基于MLE的RAIM改进方法
查看>>
自动驾驶汽车GPS系统数字孪生建模(一)
查看>>
自动驾驶汽车GPS系统数字孪生建模(二)
查看>>
CUDA 学习(五)、线程块
查看>>
CUDA 学习(八)、线程块调度
查看>>
CUDA 学习(九)、CUDA 内存
查看>>
CUDA 学习(十一)、共享内存
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十四章 生化尖兵
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十五章 超级马里奥64
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十七章 游戏感的原理
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十八章 我想做的游戏
查看>>
游戏设计的艺术:一本透镜的书——第十章 某些元素是游戏机制
查看>>
游戏设计的艺术:一本透镜的书——第十一章 游戏机制必须平衡
查看>>
游戏设计的艺术:一本透镜的书——第十二章 游戏机制支撑谜题
查看>>