for循环可以删除集合元素吗,往往我们得到的答案有时候就是不可以,安全起见,要迭代器,包括我在阿里的开发规范里也写了这么一句话, 不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式,如果并发操作,需要对 iterator 对象加锁

当然,他说的可以是怕某些人对下面的我的方法的微操有不注意的地方,所以不如一开始就说不可以。

依然记得刚来第三天写个接口我就for循环内删除元素,当时很沙雕,恰好又被代码走查看到了,尴尬的我挖了个洞将for改成了迭代器方式遍历,这两天看个大佬的代码,他就是for循环并remove其中元素,我开心的以为发现了一个bug,嗯,再往下看不对,这代码妙啊,百度了一下,有了这篇文章 下面我们通过几个例子以及分析源码的方式来看看问题,nice

问题一

List<String> list = new ArrayList();
list.add("111");
list.add("222");
list.add("222");
list.add("333");
list.add("222");
list.add("555");
//list.stream().forEach(System.out::println);
for(int i = 0;i < list.size();i++){
if(StrUtil.equals("222",list.get(i))){list.remove(i);  }}
复制代码

我们先看下上面这个用例,这个结果是啥呢?是111 222 333 555,咦,明明等于222的移除了啊,怎么没移掉,而且还没报错,通常我们移除元素会报错呀,其实这种for方法在我们循环遍历的时候list.remove(i);会删除对应的元素不会报错,但是呢,删除的元素位置会空出来,后面的元素会往前移一位,这样如果有两个元素的位置是连续的话,那么后面这个元素是不会进行判断的,这样就不会符合我们的分析场景的,

这里我们点进remove方法中会发现一个rangeCheck方法,它会先检查给定的索引是否在范围内

我们按代码顺序翻一下,索引在范围内,则获取remove的元素,然后将list的元素大小减一,如果还存在,就进行元素的copy,从源数组的index+1位置开始要复制的数组元素的数量numMoved,到目标数组的指定位置,然后通过GC将最后一个位置内存回收,哦。原来是这样的,至于说的报错我们下面在分析

问题二

for (String ll : list) {    if(StrUtil.equals(ll,"333")){  list.remove(ll);   }}
复制代码

如上代码,当我们使用foreach的时候我们需要remove的是一个对象,而不是for时的下标,这里会报错java.util.ConcurrentModificationException,这就是我们说的**报错**了,我先把结果说了吧,这里我们删除元素的话其实并不会报错,报错的是for循环哪里,在你remove后下一次遍历的时候才会报错,报异常的方法是java.util.ArrayList$Itr.checkForComodification,一看就是方法里的迭代器报错,下面我们看看为啥???

我们先看这里的for循环在做啥

这里有两个变量,cursor:下一个元素索引,lastRet:上一个元素索引,
复制代码

刚第一次进来的时候会将修改的次数赋值给expectedModCount,然后执行下面的next方法,返回遍历的值,当匹配上需要remove的时候我们看下remove方法,到ArrayList的remove方法瞅瞅;

我们看到匹配上执行fastRemove(index);

这里会给modCount++;**modCount修改的次数这里加一了哈,**然后System.arraycopy赋值,执行完后,返回true,继续遍历,再进到next的方法里遍历时执行 checkForComodification();点进去看

如果他们不相等则抛出异常,而刚刚我们remove的时候modCount值被修改了,所以抛出异常,我想到了这里,大家应该看出问题的所在了,

  1. 一个是删除后元素位置前挪了导致连续相等的元素判断不到
  2. 一个是删除元素后改动的次数变得和期望变动的次数不一样了导致的这些异常信息

解决及规避

好了,既然知道问题的原因了,那么我们该怎么规避呢???

对于第一个问题,既然删除后它会前移,那我逆序遍历是不是就好了呢(逻辑鬼才),毕竟我for的size是动态变化的

for(int i = list.size()-1;i>=0;i--){    if(StrUtil.equals("222",list.get(i))){        String remove = list.remove(i);   System.out.println("shanchu"+ remove);}}
复制代码

这个结果是啥;不用我说了吧**shanchu222 shanchu222 shanchu222 111 333 555,**你看完美解决问题,这是一个解决办法,所以逆序的情况你不必太在意;

对于第二种情况

我们用迭代器看看

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){   if(StrUtil.equals("222",iterator.next())){  list.remove(iterator.next());   }}
复制代码

它的expectedModCount是初始的6,对于list的remove这里依然会调用上面说的remove对象的方法,所以依然报错毫无疑问,我们就暂不理会,我们接着看(这里我们注意一下iterator.next()这个方法,这个其实就是for循环里为我们做的遍历的处理一样,只不过for循环本身为我们做了,可以上翻next方法),好了,接下来我们看看迭代器iterator为我们提供的remove方法,在我们执行remove方法的时候我们看看它做了什么

它依然会调用checkForComodification()进行判断,(这里我要说明一下这个remove方法依然是ArrayList类里的private class Itr implements Iterator 类里的方法)然后执行ArrayList.this.remove(lastRet);这里的lastRet是上面的cursor赋的值,由于这里是正序的,remove会将元素向左移动所以cursor会被从下一个值拽回来到lastRet的位置,lastRet给-1;然后将expectedModCount = modCount;咦,这里就是将期望修改次数的值和修改次数又同步起来了不是;

不知道这里你们发现没有,其实迭代器的remove方法和我们最上面的用例删除元素下标调用的方法一样,只不过迭代器后面又跟了点东西;比如删除后遍历的下标前移,修改的次数同步

好啦好啦,解决方法也写了,大家应该知道原因了,并且知道解决办法了,我觉得看完了就知道知其然知其所以然了,

对于线程安全的情况,如果想要保证线程安全,我们可以使用CopyOnWriteArrayList

防止有些人不看,我贴张图

咦,加了个ReentrantLock,怪不得呢,其他的我觉得和上面的for都是异曲同工了,

在for循环List中删除元素~相关推荐

  1. mysql for 循环删除_Java增强for循环中删除元素抛异常问题

    前言 最近突然想起刚毕业那会找工作时面试被问了个这样的问题.就是"使用增强for循环遍历ArrayList(List集合)时删除其中的元素是否会出现异常?".说实话当时真把我愣住了 ...

  2. 如何在C ++中从容器中删除元素

    How to remove elements from container is a common C++ interview question, so you can earn some brown ...

  3. 从PHP中的数组中删除元素

    有没有一种简单的方法可以使用PHP从数组中删除元素,以便foreach ($array)不再包含该元素? 我认为将其设置为null可以做到,但是显然不起作用. #1楼 如果您有一个数字索引的数组,其中 ...

  4. C# - 在foreach中删除元素

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在for ...

  5. List与Map的遍历过程中删除元素

    在日常的开发过程中,经常需要对List或Map里面的符合某种业务的数据进行删除,但是如果不了解里面的机制就容易掉入"陷阱"导致遗漏或者程序异常. List遍历过程中删除元素 使用索 ...

  6. 如何在Java中处理ConcurrentModificationException? 在循环中从ArrayList中删除元素时要当心...

    从Java中从ArrayList中删除元素时常见的问题之一是ConcurrentModificationException. 如果您对索引使用经典的for循环或增强的for循环,并尝试使用remove ...

  7. 【重难点】【Java集合 02】Set、List、Map 的区别、常见的线程安全的集合类、Collection 为什么只能在 Iterator 中删除元素

    [重难点][Java集合 02]List.Set.Map 的区别.常见的线程安全的集合类.Collection 为什么只能在 Iterator 中删除元素 文章目录 [重难点][Java集合 02]L ...

  8. 如何通过索引从列表中删除元素?

    如何在Python中按索引从列表中删除元素? 我找到了list.remove方法,但是说我想删除最后一个元素,该怎么做? 似乎默认的remove搜索列表,但是我不希望执行任何搜索. #1楼 pop还可 ...

  9. java数组中删除元素或一个数组元素

    java数组中删除元素或一个数组元素 删除数组中单个元素 删除数组中多个元素(数组表示) 这里直接介绍两种情况: 1.删除数组中单个元素 2.删除数组中多个元素(数组表示) 删除数组中单个元素 1. ...

最新文章

  1. 第二节 数学基础与语言学基础
  2. nodejs全局变量第一次没赋值要第二次才有是为什么_【NodeJS】async 和 await 的本质...
  3. 线性表的动态顺序存储和实现(C语言实现)【线性表】(4)
  4. boost::gil模块实现打包像素格式的测试程序
  5. git拉取tag_不给队友拖后腿!团队开发中 Git 最佳实践
  6. 【OS学习笔记】一 处理器、内存和指令
  7. [Bugku][Web][CTF] 16-29 write up
  8. Zabbix中文模式:图片下面的字符乱码
  9. Go泛型草案设计简明指南
  10. 学计算机不会重装系统正常吗,系统重装不了的原因是什么 重装不了系统的解决方法【图文】...
  11. 【PATL1-46】整除光棍(模拟除法)---水题
  12. Bash 编程易犯的错误大全
  13. excel如何批量查询手机号归属地
  14. 机器人语音问答流程及借助百度API Python实现
  15. 摆脱垃圾服务商 选择微空间免费空间
  16. 旗正规则引擎的产品设计
  17. Centos7 定时关机
  18. Django REST framework+Vue 打造生鲜超市(二)
  19. 曾国藩经典人生哲理语录
  20. echarts地图实现地区下钻

热门文章

  1. 绘画系统(06):【类】QPaintPath[官翻]
  2. setState说明
  3. springboot内嵌Tomcat启动失败
  4. 进程的三种状态及其转换
  5. 智慧节能系统的商业模式
  6. 加拿大海外仓详解:加拿大海外仓有哪些服务项目?优势在哪里
  7. console.log控制台趣味输出
  8. Linux 常用命令缩写由来
  9. 商业软文怎么写?五个步骤,让你写出高质量的文案,亲测有效
  10. Fedora18 安装后风扇不停的转