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

JavaJDK源码分析之for循环删除异常原理以及解决办法

一、本地环境编辑器:IntelliJIDEA2017JDK版本:jdk1.8二、引发的现象在一些业务中,我们会用到遍历集合然后找到需要删除

一、本地环境

  • 编辑器:IntelliJ IDEA 2017

  • JDK版本:jdk 1.8

二、引发的现象

在一些业务中,我们会用到遍历集合然后找到需要删除的元素进行remove,并且可能会删除多个元素。例如在管理群组时会踢人,踢人的业务逻辑大概是遍历这个群的人员关系,找到需要踢的人然后把他移除。

List list &#61; new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");for (String s : list) {if ("b".equals(s)) {list.remove(s);}
}

比如这么一段代码&#xff0c;就会抛一下异常

java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.lonelycountry.test3.Test1.test3(Test1.java:68)

但是有的同学比较幸运&#xff0c;写了下面这段代码&#xff0c;巧妙或者说是碰巧躲避了这个陷阱

List list &#61; new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");for (String s : list) {if ("b".equals(s)) {list.remove(s);break;}
}

三、解决方案

1、在只需要删一次集合内部元素的代码上加上break

List list &#61; new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");for (String s : list) {if ("b".equals(s)) {list.remove(s);break;}
}

2、使用迭代器

List list &#61; new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");Iterator iterator &#61; list.iterator();
while (iterator.hasNext()) {String s &#61; iterator.next();if ("b".equals(s)) {iterator.remove();}
}

四、查看源码找问题

1、遍历方法源码&#xff08;这里会以ArrayList作为例子&#xff09;

使用for循环遍历集合和使用迭代器遍历集合使用的是同一个方法&#xff0c;在ArrayList类中有个内部类实现了Iterator接口&#xff0c;有个叫next()的方法就是遍历方法&#xff08;只截了部分方法&#xff09;。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private class Itr implements Iterator<E> {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()方法第一行调用了checkForComodification()方法&#xff0c;这个方法是用来校验有没有非法操作的。

final void checkForComodification() {//左边是操作次数&#xff0c;只要集合有了元素改变&#xff0c;就会modCount&#43;&#43;&#xff0c;右边是预判操作数&#xff0c;//大家可能会想到了&#xff0c;就是调用某个方法时预判操作数和实际操作数没有同步&#xff0c;结果就出问题了if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();
}

2、ArrayList自身的remove(Object o)方法源码分析

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {public boolean remove(Object o) {if (o &#61;&#61; null) {for (int index &#61; 0; index if (elementData[index] &#61;&#61; null) {fastRemove(index);return true;}} else {for (int index &#61; 0; index if (o.equals(elementData[index])) {fastRemove(index); //关注这个方法就行return true;}}return false;}private void fastRemove(int index) {modCount&#43;&#43;; //就是它导致的&#xff01;&#xff01;&#xff01;&#xff01;int numMoved &#61; size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index&#43;1, elementData, index, numMoved);elementData[--size] &#61; null; // clear to let GC do its work}
}

remove(Object o)方法中&#xff0c;遍历找到了需要删除的索引&#xff0c;校验索引有效后&#xff0c;执行了fastRemove(int index)方法。这个方法第一步就是对实际操作数&#43;1&#xff0c;然后进行了数组操作&#xff0c;而这时expectedModCount没有变化&#xff0c;可想而知&#xff0c;当再执行next()方法的时候&#xff0c;checkForComodification()方法肯定会抛出异常。

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

在群组操作上使用了System类的arraycopy()方法&#xff0c;底层调用的应该是C或者C&#43;&#43;的方法&#xff08;我猜的&#xff09;&#xff0c;我查了下文档&#xff0c;大概说下逻辑。

index(索引)srcPos开始取src数组的元素&#xff0c;长度为length&#xff0c;然后覆盖dest数组的destPos位置&#xff08;注意是覆盖&#xff0c;不是插入&#xff09;。但是为什么在操作群组上用这么复杂的方法我就不得而知了&#xff0c;有对算法精通的骚年可以指导下我哦。

3、ArrayList迭代器内部类的remove()方法源码分析

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private class Itr implements Iterator<E> {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();}}}
}

这个方法其实本质还是调用了ArrayList自身的remove(int index)方法&#xff0c;和上面的方法大同小异&#xff0c;但是在后面偷偷做了个实际操作数和预判操作数同步&#xff0c;就是因为这行逻辑导致使用迭代器遍历中删除不会抛异常。肯定有人疑惑既然知道了原因&#xff0c;为什么不在remove(Object o)方法中加一行同步呢&#xff0c;这是不行的&#xff0c;因为expectedModCount变量是内部类Itr中的变量&#xff0c;而remove(Object o)方法是ArrayList外部声明的&#xff0c;无法操作干扰内部类的变量

五、总结

主要分析了两种遍历集合的代码区别&#xff0c;以及出错的位置和原因。当然也有个疑惑就是在调用fastRemove(int index)方法时&#xff0c;为什么要使用System.arraycopy()方法。



推荐阅读
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
author-avatar
神话海青_769
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有