热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

遍历list集合时删除元素出现的异常

遍历list集合时删除元素出现的异常publicvoidtest(){ArrayListstringsnewArrayList();strings.add(1

遍历list集合时删除元素出现的异常

public void test() {ArrayList strings &#61; new ArrayList<>();strings.add("1");strings.add("2");for (String item : strings){if("2".equals(item)){strings.remove(item);}}System.out.println(strings.toString());}

出现错误&#xff1a;
java.util.ConcurrentModificationException
at java.util.ArrayListItr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.checkForComodification(ArrayList.java:909) at java.util.ArrayListItr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.next(ArrayList.java:859)
at test.test(test.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner2.evaluate(ParentRunner.java:268)atorg.junit.runners.ParentRunner.run(ParentRunner.java:363)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)atcom.intellij.rt.junit.IdeaTestRunner2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner2.evaluate(ParentRunner.java:268)atorg.junit.runners.ParentRunner.run(ParentRunner.java:363)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)atcom.intellij.rt.junit.IdeaTestRunnerRepeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

异常产生
当我们迭代一个ArrayList或者HashMap时&#xff0c;如果尝试对集合做一些修改操作&#xff08;例如删除元素&#xff09;&#xff0c;可能会抛出java.util.ConcurrentModificationException的异常。

异常原因
ArrayList的父类AbstarctList中有一个域modCount&#xff0c;每次对集合进行修改&#xff08;增添元素&#xff0c;删除元素……&#xff09;时都会modCount&#43;&#43;

而foreach的背后实现原理其实就是Iterator&#xff08;关于Iterator可以看Java Design Pattern: Iterator&#xff09;&#xff0c;等同于注释部分代码。在这里&#xff0c;迭代ArrayList的Iterator中有一个变量expectedModCount&#xff0c;该变量会初始化和modCount相等&#xff0c;但如果接下来如果集合进行修改modCount改变&#xff0c;就会造成expectedModCount !&#61; modCount&#xff0c;此时就会抛出java.util.ConcurrentModificationException异常

源码解释
1&#xff1a;ConcurrentModificationException异常与modCount这个变量有关。
2&#xff1a;modCount&#xff1a;modCount就是修改次数&#xff0c;在具体的实现类中的Iterator中才会使用。在List集合中&#xff0c;ArrayList是List接口的实现类&#xff0c;

modCount&#xff1a;表示list集合结构上被修改的次数。&#xff08;在ArrayList所有涉及结构变化的方法中&#xff0c;都增加了modCount的值&#xff09;

list结构上别修改是指&#xff1a;改变了list的长度的大小或者是遍历结果中产生了不正确的结果的方式。add()和remove()方法会是modCount进行&#43;1操作。modCount被修改后会产生ConcurrentModificationException异常&#xff0c; 这是jdk的快速失败原则

3&#xff1a;modCount的变量从何而来。
modCount被定义在ArrayList的父类AbstractList中&#xff0c;初值为0&#xff0c;protected transient int modCount &#61; 0。

4&#xff1a;上述代码用来4个方法&#xff0c;操作集合&#xff0c;add(),next(),hasNext(),remove(),这四个方法。
&#xff08;1&#xff09;ArrayList中add()的源代码。

public boolean add(E e) {ensureCapacityInternal(size &#43; 1); // Increments modCount!!elementData[size&#43;&#43;] &#61; e;return true;
}

在进行add()方法是&#xff0c;先调用的ensureCapacity()这个方法&#xff0c;来判断是否需要扩容。

private void ensureCapacityInternal(int minCapacity) {if (elementData &#61;&#61; DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity &#61; Math.max(DEFAULT_CAPACITY, minCapacity);}//看集合是否为空集合。很显然&#xff0c;并不是空集合。ensureExplicitCapacity(minCapacity); //
}

在判断是否需要扩容时&#xff0c;用调用ensureExplicitCapacity这个方法&#xff0c;注意&#xff0c;这个方法中对modCount进行的加一操作。

private void ensureExplicitCapacity(int minCapacity) {modCount&#43;&#43;;if (minCapacity - elementData.length > 0)grow(minCapacity);
}

所以集合在进行添加元素是会对modCount进行&#43;1的操作。

在进行文章开头的代码时list集合中使用add添加了元素后&#xff0c;size&#61;2&#xff0c;modCount&#61;2&#xff0c;

&#xff08;2&#xff09;Iterator的方法是返回了一个Itr()的类的一个实例。

public Iterator iterator() { return new Itr(); }

Itr是ArrayList的一个成员内部类。

private class Itr implements Iterator {int cursor; // cursor&#xff1a;表示下一个要访问的元素的索引int lastRet &#61; -1; // int expectedModCount &#61; modCount;public boolean hasNext() {return cursor !&#61; size;}&#64;SuppressWarnings("unchecked")public E next() {checkForComodification();int i &#61; cursor;if (i >&#61; size)throw new NoSuchElementException();Object[] elementData &#61; ArrayList.this.elementData;if (i >&#61; elementData.length)throw new ConcurrentModificationException();cursor &#61; i &#43; 1;return (E) elementData[lastRet &#61; i];}public void remove() {if (lastRet <0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor &#61; lastRet;lastRet &#61; -1;expectedModCount &#61; modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}&#64;Override&#64;SuppressWarnings("unchecked")public void forEachRemaining(Consumer consumer) {Objects.requireNonNull(consumer);final int size &#61; ArrayList.this.size;int i &#61; cursor;if (i >&#61; size) {return;}final Object[] elementData &#61; ArrayList.this.elementData;if (i >&#61; elementData.length) {throw new ConcurrentModificationException();}while (i !&#61; size && modCount &#61;&#61; expectedModCount) {consumer.accept((E) elementData[i&#43;&#43;]);}// update once at end of iteration to reduce heap write trafficcursor &#61; i;lastRet &#61; i - 1;checkForComodification();}final void checkForComodification() {if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();}
}

在for循环内部首先对Iterator进行了初始化&#xff0c;初始化时&#xff0c;expectedModCount&#61;2&#xff0c;cursor&#61;0。

&#xff08;2.1&#xff09;单看hasnext()方法

public boolean hasNext() {return cursor !&#61; size();
}

hasNext是判断是否右下一个元素&#xff0c;判断条件是cursor不等于size&#xff0c;有下一个元素。size&#61;2,cursor&#61;0&#xff0c;是有下一个元素的。

&#xff08;2.2&#xff09;单看next()方法

public E next() {checkForComodification();int i &#61; cursor;if (i >&#61; size)throw new NoSuchElementException();Object[] elementData &#61; ArrayList.this.elementData;if (i >&#61; elementData.length)throw new ConcurrentModificationException();cursor &#61; i &#43; 1;return (E) elementData[lastRet &#61; i];
}

当进行next()方法时&#xff0c;调用了checkForComodification()方法&#xff0c;进行判断是否抛出异常

final void checkForComodification() {if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();
}

此时&#xff0c;modCount&#61;expectedModCount&#61;2&#xff0c;不抛异常&#xff0c;程序继续进行&#xff0c;next()方法有对cursor进行了加一的操作。cursor&#61;1。

item&#61;“1”,继续遍历&#xff0c;hasNext判断是否右下一个元素&#xff0c;有下一个元素。size&#61;2,cursor&#61;1&#xff0c;是有下一个元素的。item&#61;“2”&#xff0c;此时进行remove删除操作。

&#xff08;3&#xff09;ArrayList中remove的源代码。

public boolean remove(Object o) {if (o &#61;&#61; null) {for (int index &#61; 0; index }

可以看见进行remove操作时&#xff0c;是通过调用fastRemove()方法进行的实际删除。在fastRemove()方法中&#xff0c;对modCount进行了&#43;1的操作。

private void fastRemove(int index) {modCount&#43;&#43;;int numMoved &#61; size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index&#43;1, elementData, index,numMoved);elementData[--size] &#61; null;
}

在fastRemove()方法中&#xff0c;modCount进行了&#43;1操作&#xff0c;modCount&#61;3&#xff0c;size进行了-1的操作&#xff0c;size&#61;1&#xff0c;程序继续进行&#xff0c;进行next()方法&#xff0c;发现modCount不等于expectedModCount&#xff08;expectedModCount&#61;2&#xff09;&#xff0c;抛出了ConcurrentModificationException异常。

解决办法&#xff1a;
1.单线程&#xff1a;

Iterator iter &#61; list.iterator();
while(iter.hasNext()){String str &#61; iter.next();if( str.equals("B") ){iter.remove();}
}

Itr中的也有一个remove方法&#xff0c;实质也是调用了ArrayList中的remove&#xff0c;但增加了expectedModCount &#61; modCount;保证了不会抛出java.util.ConcurrentModificationException异常。

但是&#xff0c;这个办法的有两个弊端
1.只能进行remove操作&#xff0c;add、clear等Itr中没有。
2.而且只适用单线程环境。

2.多线程环境
在多线程环境下&#xff0c;我们再次试验下上面的代码

public class Test2 {static List list &#61; new ArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName() &#43; ":"&#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName() &#43; ":"&#43; element);if (element.equals("c")) {iterator.remove();}}};}.start();}
}

Output:
在这里插入图片描述

异常的原因很简单&#xff0c;一个线程修改了list的modCount导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等。

此时有两个办法&#xff1a;

迭代前加锁&#xff0c;解决了多线程问题&#xff0c;但还是不能进行迭代add、clear等操作

public class Test2 {static List list &#61; new ArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();synchronized (list) {while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()&#43; ":" &#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();synchronized (list) {while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName()&#43; ":" &#43; element);if (element.equals("c")) {iterator.remove();}}}};}.start();}
}

采用CopyOnWriteArrayList&#xff0c;解决了多线程问题&#xff0c;同时可以add、clear等操作

public class Test2 {static List list &#61; new CopyOnWriteArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()&#43; ":" &#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName()&#43; ":" &#43; element);if (element.equals("c")) {list.remove(element);}}};}.start();}
}

CopyOnWriteArrayList也是一个线程安全的ArrayList&#xff0c;其实现原理在于&#xff0c;每次add,remove等所有的操作都是重新创建一个新的数组&#xff0c;再把引用指向新的数组。


推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文总结和分析了JDK核心源码(2)中lang包下的基础知识,包括常用的对象类型包和异常类型包。在对象类型包中,介绍了Object类、String类、StringBuilder类、StringBuffer类和基本元素的包装类。在异常类型包中,介绍了Throwable类、Error类型和Exception类型。这些基础知识对于理解和使用JDK核心源码具有重要意义。 ... [详细]
  • 本文介绍了在实现了System.Collections.Generic.IDictionary接口的泛型字典类中如何使用foreach循环来枚举字典中的键值对。同时还讨论了非泛型字典类和泛型字典类在foreach循环中使用的不同类型,以及使用KeyValuePair类型在foreach循环中枚举泛型字典类的优势。阅读本文可以帮助您更好地理解泛型字典类的使用和性能优化。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
  • 本文介绍了如何使用OpenXML按页码访问文档内容,以及在处理分页符和XML元素时的一些挑战。同时,还讨论了基于页面的引用框架的局限性和超越基于页面的引用框架的方法。最后,给出了一个使用C#的示例代码来按页码访问OpenXML内容的方法。 ... [详细]
  • 在本教程中,我们将看到如何使用FLASK制作第一个用于机器学习模型的RESTAPI。我们将从创建机器学习模型开始。然后,我们将看到使用Flask创建AP ... [详细]
author-avatar
书友67997456_296
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有