文章目录

  • 前言
  • 快速失败 vs 安全失败
    • 快速失败(fail-fast)
      • 原理
      • 场景:
    • 安全失败(fail-safe)
      • 原理
    • 场景
  • 实战
    • ArrayList
      • 增强 for 循环
      • 普通 for 循环
      • *Iterator 方式
    • 总结
  • 源码解析
    • ArrayList.remove
      • 根据下标删除元素
      • 根据元素删除元素
      • 普通 for 循环
      • 增强 for 循环
      • 迭代器

前言

以前老早就学习过如何在循环遍历 List 的情况下操作(新增/删除)。现在在项目中又碰到了一次,所以在此记录一下,以免有些遗忘,还得去 Google 。直接翻自己的博客就行了。

快速失败 vs 安全失败

快速失败(fail-fast)

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),就会抛出ConcurrentModificationException。

原理

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返遍历;否则抛出异常,终止遍历。
Tip:这里异常的抛出条件是检测到 modCount=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。「比如,删除成功后,数据和你想要的最终结果不一致,但是没报错;还有一种是你原本的数据就刚好,你当前的数据跑完代码是正确的,但是其他数据就不一定了」

场景:

java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

安全失败(fail-safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历

原理

优势:迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对源集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
缺点:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景

java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用。

实战

ArrayList

**背景:**我是使用 fastjson 进行 json 串整合的时候碰到的,但是 JSONArray 使用的是 ArrayList;
「JSONObject 底层使用 HashMap 的删除函数」。

增强 for 循环

public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("1");list.add("2");list.add("3");list.add("4");for (String s:list) {if(s=="2"){       // tips:当 s=="3" ,会成功删除元素// 当等于 list 中其他三个元素时都会报错:ConcurrentModificationExceptionlist.remove("2");}}log.info("before:"+list);
}//在操作后 break:马上跳出循环,则不会报错
for (String s:list) {if(s=="2"){list.remove("2");break;}
}

普通 for 循环

不会报错,但是取得值可能会出现异常**

这主要是因为删除元素后,被删除元素后的元素索引发生了变化。假设被遍历list中共有10个元素,当 删除了第3个元素后,第4个元素就变成了第3个元素了,第5个就变成 了第4个了,但是程序下一步循环到的索引是第4个, 这时候取到的就是原本的第5个元素了。 比如下面的程序:

public static void main(String[] args) {List<String> list=new ArrayList<String>();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");list.add("6");list.add("7");list.add("8");for(int i=0;i<list.size();i++){if(i%3==0){list.remove(i);}}//逆向的 for 循环可以消除元素移动的影响,打印正常//for(int i=list.size()-1;i>=0;i--){//    if(i%3==0){//        list.remove(i);//    }//}log.info("after:"+list);}

打印结果如下:

- after:[2, 3, 4, 6, 7, 8]

按照代码逻辑:本来正确的应该是:- after:[2, 3, 5, 6, 8]

*Iterator 方式

这种方式可以正常删除,建议使用。

public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("1");  //list.add("2");list.add("3");list.add("4");  //list.add("5");list.add("6");list.add("7");  //list.add("8");Iterator<String> iterator=list.iterator();while (iterator.hasNext()){String s=iterator.next();if((Integer.parseInt(s)-1)%3==0){iterator.remove();  //***//list.remove(s);  这里要使用 Iterator的remove 方法移除当前对象,// 如果使用 List 的 remove 方法,则同样会出现 ConcurrentModificationException}}log.info("after:"+list);
}

总结

  1. 增强 for 循环:用 break,可以防止报错
  2. for 循环:用反向(反向遍历)删除可以正常运行
  3. 必须用 Iterator 迭代器自带的 remove 函数。

源码解析

ArrayList.remove

根据下标删除元素

public E remove(int index) {rangeCheck(index);   //检查是否越界modCount ++;          //保存修改的次数E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)//将数组中的某一段数据取出来放到另一个数组中System.arraycopy(elementData, index + 1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}
src      the source array.
srcPos   starting position in the source array.
dest     the destination array.
destPos  starting position in the destination data.
length   the number of array elements to be copied.
public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,          int length);

删除一个元素后,会重新改变 ArrayList 中的 elementData,而这个 elementData 就代表 ArrayList 的值

根据元素删除元素

//也是相当于通过 下标删除元素

public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;
}private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its work}

普通 for 循环

就是按照上面的 remove 方法的源码运行,不会报错!

增强 for 循环

public Iterator<E> iterator() {return new Itr();
}Itr():是 ArrayList 中实现 Iterator 的内部类private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;Itr() {}// 这个 hasNext 很关键,当遍历到倒数第二个的时候,cursor刚好等于size//所以当 if 条件是倒数第二个的时候,不会报错。/*返回的是:增强的 for()括号中的值*/public boolean hasNext() {return cursor != size;}// 每运行一次 next() 就会 cursor + 1@SuppressWarnings("unchecked")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];}//当两者不等时,final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}....../*
在第一次初始化的时候会把 modCount 赋值给 expectedModCount;进行增加或者删除,modCount(记录改变次数的)都会加一,所以两者不相等时,会报错异常 ConcurrentModificationException
*/

迭代器

//在 ArrayList 内部实现类中
/*
调用 ArrayList Itr 内部类的的 remove 方法
*/
public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}

Java 正确循环遍历 List 删除相关推荐

  1. [java] Map循环遍历的5种方法实现

    [java] Map循环遍历的5种方法实现 文章目录 一.方法一(推荐) 二.方法二(推荐) 三.方法三 四.方法四 五.方法五 总结 一.方法一(推荐) 推荐使用此方法效率比较高 Map<St ...

  2. 【java迭代器Iterator】获取单个元素,循环遍历和删除

    java迭代器Iterator 什么是Iterator 怎么获取Iterator对象 Iterator三个常用方法 E next() 获取单个元素 boolean hasNext() 可用于循环遍历 ...

  3. 正确在遍历中删除List元素

    最近在写代码的时候遇到了遍历时删除List元素的问题,在此写一篇博客记录一下. 一般而言,遍历List元素有以下三种方式: 使用普通for循环遍历 使用增强型for循环遍历 使用iterator遍历 ...

  4. java 目录的遍历与删除

    ----------------------目录的遍历 package zhi_jie_liu;import java.io.*;public class Example27 {public stat ...

  5. Java(2): java for循环遍历数组

    一.Java 增强 for 循环 for(声明语句 : 表达式) { //代码句子 } 声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配.其作用域限定在循环语句块,其值与此时数组元素的 ...

  6. java for循环遍历解释,三种for循环遍历

    import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class  For{ pub ...

  7. java 枚举可以循环吗_(转载)java 枚举 循环遍历以及一些简单常见的使用

    本文转载自:http://blog.csdn.net/qq_27093465/article/details/51706076 作者:李学凯 什么时候想用枚举类型: 有时候,在设计一个java mod ...

  8. java for循环遍历解释_三种for循环遍历

    import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class  For{ pub ...

  9. java枚举类循环_(转载)java 枚举 循环遍历以及一些简单常见的使用

    本文转载自:http://blog.csdn.net/qq_27093465/article/details/51706076 作者:李学凯 什么时候想用枚举类型: 有时候,在设计一个java mod ...

最新文章

  1. android 代码设置inputtype,android – 如何正确设置EditText的InputType?
  2. SQL Server T-SQL高级查询
  3. 结合源码分析 bubble 使用注意事项
  4. Python学习笔记011_模块_标准库_第三方库的安装
  5. JDK源码解析之 Java.lang.Compiler
  6. 使用base64编码把背景添加到CSS文件中
  7. SpringCloud集成Security安全(Config配置中心)
  8. Windows用WinDbg分析蓝屏dump文件查找原因(转)
  9. 量子计算机网络指数时间,科普:量子计算机是这样计算的
  10. 梯度下降python编程实现_【机器学习】线性回归——单变量梯度下降的实现(Python版)...
  11. 如何简洁优雅地实现Kubernetes的服务暴露
  12. java笔记--查看和修改线程名称
  13. 【C++】std::是什么?
  14. 为什么函数lamda显示权限不足_C++常用内置函数
  15. Maximum call stack size exceeded 如何解决?
  16. 问题服务器防火墙的选择
  17. 区块链溯源是如何实现的?
  18. Rabbitmq消息中心_消息中心总体方案
  19. 维基解密想帮助苹果、Google 对抗 CIA 黑客入侵
  20. notes 常见问题

热门文章

  1. 详解三种Menu——通俗易懂
  2. mysql如何进行视图恢复_mysql事务 视图 索引 备份和恢复
  3. Linux内核中的内存管理(图例解析)
  4. 下一座“金矿”:移动医疗的契机和风险
  5. 【集成学习-组队学习】2.使用sklearn构建完整的机器学习项目流程
  6. java不是有效的win32_jdk不是有效的win32程序解决办法
  7. 一个独特的开源插件evil.js
  8. Bilibili直播姬使用obs的一些证据之类的
  9. 多实例数据库应用PSU
  10. sp工具中最疼的是_阴阳师SP酒吞就业面详解 高级PVE新工具人 食发鬼:别说了太难了...