集合遍历过程中删除元素报错ConcurrentModificationException(并发修改异常)
今天敲代码时遇到一个异常,代码如下:
List<Integer> list = new ArrayList<>();
list:[50, 42, 56, 76, 62, 38, 42, 98, 66, 91, 53, 56, 97, 61, 33, 26, 97, 45]
//遍历list去除奇数
Iterator<Integer> iterator = list.iterator();//获取ArrayList的迭代器
//迭代器遍历
while(iterator.hasNext()){
Integer tempNum = iterator.next();
if (tempNum % 2 != 0){
list.remove(tempNum);//报错ConcurrentModificationException(并发修改异常)
}
}
查询资料后发现ConcurrentModificationException是Java集合的一个快速失败(fail-fast)机制,防止多个线程同时修改同一个集合的元素。
直接使用list对象调用remove方法list.remove()
,改变了modCount的值,但是没有改变expectedModCount的值,如果此时继续调用iterator的方法,就会出现ConcurrentModificationException(并发修改异常)。
有关错误判断机制请继续往下看:
当获取到ArrayList的迭代器iterator
时会返回一个Itr对象,通过它里面的remove来删除元素
iterator.remove()
->该方法会自动修改expectedModCount的值,保证其与modCount一致,这样就不会报错了.
ArrayList部分源码:
public Iterator<E> iterator() {
return new Itr();
}
ArrayList中的私有类**Itr
**类部分源码:
private class Itr implements Iterator<E> {
//游标。指向下一个返回的元素
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
//修改次数,用于检测在迭代器遍历过程中集合是否发生改变
//modCount -> 当前状态下的集合修改次数
//ecpectedModCount -> 遍历过程中的集合修改次数
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
......
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//调用该方法会自动修改expectedModCount的值,使得它和当前集合的修改次数一致
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
......
//错误检测方法,通过比较modCount与expectedModCount是否一致,不一致则抛出异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
了解到这些之后,正确代码如下:
List<Integer> list = new ArrayList<>();
list:[50, 42, 56, 76, 62, 38, 42, 98, 66, 91, 53, 56, 97, 61, 33, 26, 97, 45]
//遍历list去除奇数
Iterator<Integer> iterator = list.iterator();//获取ArrayList的迭代器
//迭代器遍历
while(iterator.hasNext()){
Integer tempNum = iterator.next();
if (tempNum % 2 != 0){
iterator.remove();//代码不会报错了
}
}
注意:
- ArrayList可以使用普通for循环来遍历,效率可以,但是LinkedList不建议使用普通for循环遍历,数据多的话效率太差
- 对于
Map实现类HashMap
和Set的实现类HashSet
也是一样的,在迭代器遍历过程中也是不能修改的,另一种遍历方式:增强for循环其底层也是通过迭代器来遍历的。 - 这里提供一种解决方案,通过一个新的集合来存储你需要的数据,最后输出这个新的集合内容。
附加:
集合主要用到三种 :
Collection接口中的一些方法:
boolean add(E e); //添加单个元素
boolean addAll(Collection<? extends E> c); //添加一个类型为E或者其子类类型的集合
boolean remove(Object o); //删除一个元素
boolean removeAll(Collection<?> c); //删除一个集合
int size(); //返回集合大小
boolean contains(Object o); //判断集合当中是否存在 o
boolean containsAll(Collection<?> c); //判断一个集合是否在该集合当中
void clear(); //清空集合
boolean equals(Object o); //比较是否相等
boolean isEmpty(); //判断集合是否为空
Set接口:
Set接口下有一个 HashSet
实现类,HashSet的底层是用 HashMap 实现的,因此,查询效率高。由于采用Hashcode算法直接确定元素的内存地址,增删效率也高。
HashSet 接口中的元素 无序不可重复 ,不包含重复元素,最多包含一个 null,元素没
有顺序 。
创建一个Set集合:
Set<E> set = new HashSet<>();//这个E代表可以存储任意类型的数据-》泛型
Set集合遍历方式:
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ddd");
//增强for循环
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
//迭代器循环
Iterator<String> iterator = set.iterator(); //获取迭代器
while(iterator.hasNext()){ //判断是否还有下一个元素
String tempString = iterator.next(); //指向下一个元素
System.out.println(tempString);
}
}
}
//输出结果,与放进去的顺序不符,说明了set集合的无序存储
aaa
ccc
bbb
ddd
-----------------------
aaa
ccc
bbb
ddd
List接口:
List接口下有两个实现类,LinkedList 和 ArrayList都实现了List的方法,
并且都给出了可以存储有序、重复数据的空间。
LinkedList:
底层用双向链表
实现的List。特点:查询效率低,增删效率高,线程不安全。
ArrayList:
底层用数组
实现的List。特点:查询效率高,增删效率低,线程不安全。
List集合的创建:
ArrayList<E> list = new ArrayList<>();//这个E代表可以存储任意类型的数据-》泛型
list遍历:
public class ArrayListTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("y");
list.add("l");
list.add("s");
list.add("l");
System.out.println(list);
//普通for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("-----");
//增强for循环遍历
for (Object o : list) {
System.out.println(o);
}
System.out.println("-----");
//迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
//输出结果,顺序输出,和添加进去的顺序一致
[y, l, s, l]
y
l
s
l
-----
y
l
s
l
-----
y
l
s
l
Map接口:
Map只是一个接口,其中提供了Map的规范,即各种方法的定义
Object put(Object key, Object value);//添加元素
Object get(Object key);//根据key值获取value值
Object remove(Object key);//根据key值移除value值
boolean containsKey(Object key);//根据key值判断map中是否含有该key值,返回true or false
boolean containsValue(Object value);//根据value值判断map中是否含有该value值,同上
int size();//获取map的大小
boolean isEmpty();//判断map是否为空
void putAll(Map t);//将一个map直接添加到另一个map中
void clear();//清空所有元素
常见的实现类有 HashMap 、 HashTable 等
HashMap中存储数据的特点:
- 存储的元素是以K-V的形式存在的
- map集合中 key 必须要唯一,如果添加了相同的键值对(键相同)会发生覆盖
- map集合中元素(键值对)是无序的,和Set集合类似
注意: 如果存入的键值对中key相同,那么后面的会覆盖掉前面的数据。
当数据存放到Map之后,我们是无法控制存放数据的顺序的
创建一个Map集合:
Map<K, V> map = new HashMap<>();//这里K,V代表键值对的类型,都是泛型,想存什么类型就存什么类型
map遍历:
public class DemoHashMap {
public static void main(String[] args) {
Map<Integer,String> num = new HashMap<>();
num.put(1, "一,Ⅰ,壹");
num.put(2, "二,Ⅱ,贰");
num.put(3, "三,Ⅲ,叁");
num.put(4, "三,Ⅲ,叁");
System.out.println("-----------keySet-----------");
Set<Integer> keys = num.keySet();
System.out.println("---------增强for------");
for(Integer key : keys){
System.out.println(key + "-" + num.get(key));
}
System.out.println("-------迭代器-------");
Iterator<Integer> keyIt = keys.iterator();
while(keyIt.hasNext()){
// 获取下一个key
Integer key = keyIt.next();
String value = num.get(key);
System.out.println(key + "-" + value);
}
System.out.println("===========Entry的遍历==========");
Set<Entry<Integer, String>> entrySet= num.entrySet();
for(Entry<Integer, String> entry : entrySet){
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "-" + value);
}
System.out.println("-------迭代器-----------");
Iterator<Entry<Integer,String>> it = entrySet.iterator();
while(it.hasNext()){
Entry<Integer, String> en = it.next();
System.out.println(en.getKey() + "-" + en.getValue());
}
System.out.println("------------遍历所有value--------------");
Collection<String> values = num.values();
for(String v :values){
System.out.println(v);
}
System.out.println("--------");
Iterator<String> iterator = values.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
//输出结果
-----------keySet-----------
---------增强for------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
-------迭代器-------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
===========Entry的遍历==========
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
-------迭代器-----------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
------------遍历所有value--------------
一,Ⅰ,壹
二,Ⅱ,贰
三,Ⅲ,叁
三,Ⅲ,叁
--------
一,Ⅰ,壹
二,Ⅱ,贰
三,Ⅲ,叁
三,Ⅲ,叁
参考链接:
https://blog.csdn.net/shengongbao114/article/details/85006658
https://www.cnblogs.com/xiaofengcanyuelong/p/13302096.html
https://blog.csdn.net/weixin_44745208/article/details/104390703
https://www.cnblogs.com/Berryxiong/p/6134012.html
第一次写博客,如果有什么出错的地方欢迎大家指正,如果引用其他人的博客有什么不当之处,欢迎博客作者来信,立刻改正。