在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug。不妨把这个问题当做一道面试题目,我想一定能难道不少的人。今天就给大家说一下在ArrayList循环遍历并删除元素的问题。首先请看下面的例子:

  1. import java.util.ArrayList;
  2. public class ArrayListRemove
  3. {
  4.   publicstaticvoidmain(String[]args)
  5.   {
  6.     ArrayList<String>list=newArrayList<String>();
  7.     list.add("a");
  8.     list.add("b");
  9.     list.add("b");
  10.     list.add("c");
  11.     list.add("c");
  12.     list.add("c");
  13.     remove(list);
  14.     for(Strings:list)
  15.     {
  16.       System.out.println("element : "+s);
  17.     }
  18.   }
  19.   public static void remove(ArrayList<String> list)
  20.   {
  21.   // TODO:
  22.   }
  23. }

如果要想删除list的b字符,有下面两种常见的错误例子:

错误写法实例一:

  1. public static void remove(ArrayList<String> list)
  2. {
  3. for(inti=0;i<list.size();i++)
  4. {
  5. Strings=list.get(i);
  6. if(s.equals("b"))
  7. {
  8. list.remove(s);
  9. }
  10. }
  11. }

错误的原因:这种最普通的循环写法执行后会发现第二个“b”的字符串没有删掉。

错误写法实例二:

  1. public static void remove(ArrayList<String> list)
  2. {
  3. for(Strings:list)
  4. {
  5. if(s.equals("b"))
  6. {
  7. list.remove(s);
  8. }
  9. }
  10. }

错误的原因:这种for-each写法会报出著名的并发修改异常:java.util.ConcurrentModificationException。

先解释一下实例一的错误原因。翻开JDK的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

  1. public boolean remove(Objecto){
  2. if(o==null){
  3. for(intindex=0;index<size;index++)
  4. if(elementData[index]==null){
  5. fastRemove(index);
  6. return true;
  7. }
  8. }else{
  9. for(intindex=0;index<size;index++)
  10. if(o.equals(elementData[index])){
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }

一般情况下程序的执行路径会走到else路径下最终调用faseRemove方法:

  1. private void fastRemove(int index){
  2. modCount++;
  3. intnumMoved=size-index-1;
  4. if(numMoved>0)
  5.   System.arraycopy(elementData,index+1,elementData,index,numMoved);
  6. elementData[--size]=null;// Let gc do its work
  7. }

可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第一个字符串b时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时后一个字符串b并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免:

  1. public static void remove(ArrayList<String> list)
  2. {
  3. for(inti=list.size()-1;i>=0;i--)
  4. {
  5. Strings=list.get(i);
  6. if(s.equals("b"))
  7. {
  8. list.remove(s);
  9. }
  10. }
  11. }

因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。

接着解释一下实例二的错误原因。错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next方法:

  1. public E next() {
  2. checkForComodification();
  3. try {
  4. E next = get(cursor);
  5. lastRet = cursor++;
  6. return next;
  7. } catch (IndexOutOfBoundsException e) {
  8. checkForComodification();
  9. throw new NoSuchElementException();
  10. }
  11. }

第一行checkForComodification方法:

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

  1. public static void remove(ArrayList<String> list)
  2. {
  3. Iterator<String> it = list.iterator();
  4. while (it.hasNext())
  5. {
  6. String s = it.next();
  7. if (s.equals("b"))
  8. {
  9. it.remove();
  10. }
  11. }
  12. }

ArrayList循环遍历并删除元素的常见陷阱的更多相关文章

  1. 【转】ArrayList循环遍历并删除元素的常见陷阱

    转自:https://my.oschina.net/u/2249714/blog/612753?p=1 在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出b ...

  2. Java中ArrayList循环遍历并删除元素的陷阱

    ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下. 先写个测试代码: import java.util.ArrayList; public ...

  3. ArrayList循环遍历并删除元素的几种情况

    如下代码,想要循环删除列表中的元素b,该怎么处理? public class ListDemo { public static void main(String[] args) { ArrayList ...

  4. 【Java】List遍历时删除元素的正确方式

    当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...

  5. 【原理探究】女朋友问我ArrayList遍历时删除元素的正确姿势是什么?

    简介 我们在项目开发过程中,经常会有需求需要删除ArrayList中的某个元素,而使用不正确的删除方式,就有可能抛出异常.或者在面试中,会遇到面试官询问遍历时如何正常删除元素.所以在本篇文章中,我们会 ...

  6. JAVA List 一边遍历一边删除元素

    JAVA List 一边遍历一边删除元素,报java.util.ConcurrentModificationException异常 2015年02月10日 14:42:49 zhanzkw 阅读数:3 ...

  7. java list集合遍历时删除元素

    转: java list集合遍历时删除元素 大家可能都遇到过,在vector或arraylist的迭代遍历过程中同时进行修改,会抛出异常java.util.ConcurrentModification ...

  8. Java HashMap 如何正确遍历并删除元素

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K ...

  9. js 遍历集合删除元素

    js 遍历集合删除元素 /** * 有效的方式 - 改变下标,控制遍历 */ for (var i = 0; i < arr.length; i++) { if (...) { arr.spli ...

随机推荐

  1. [linux]CentOS安装pre-built Nginx

    官方文档:https://nginx.org/en/linux_packages.html Nginx安装分为软件包安装和pre-built安装.这里使用的pre-built安装,不用自己编译. 设置 ...

  2. Y1S002 xshell脚本编写示意

    SecureCRT可以自己录制脚本,非常的方便:但是考虑到CRT收费,所以不计划把CRT作为使用的终端. vbs脚本(test.vbs): Sub main xsh.Screen.Synchronou ...

  3. Tensor基本操作

    Tensor(张量) 1.Tensor,又名张量,从工程角度来说,可简单地认为它就是一个数组,且支持高效的科学计算.它可以是一个数(标量).一维数组(向量).二维数组(矩阵)或更高维的数组(高阶数组) ...

  4. 3.jmeter接口测试---脚本录制

    安装好jmeter后,就要进入主题了,进行接口测试,接口测试的脚本获取方式 ①手动填写 ②badboy录制后,导入jmeter使用 ③jmeter录制 不会安装的可以进入这里:https://www. ...

  5. 真正的ddos防御之道,简单干脆有效!

    话说,30G 就各种发博客 BB,唉,坦白说 ,博客园团队真心没见过世面 来 各位 先看图 啥意思呢? 就是哥的 最高防御是 600G.  没错,基本对当时的游戏没啥大的影响,10秒内恢复. 因为时间 ...

  6. 详谈kafka的深入浅出

    第一:kafka的介绍,kafka官网:http://kafka.apache.org/ http://www.jasongj.com/2015/03/10/KafkaColumn1/ kafka的简 ...

  7. Cheat Engine 6.8 设置中文

    下载翻译文件包 https://cheatengine.org/downloads.php 下载后解压到 "Cheat Engine 6.8.3\languages" 下面 修改 ...

  8. oracle 创建的表为什么在table里没有,但是可以查出来

    有两种的可能: 1这个表在其他用户下创建的,当前用户没有权限访问,此表不在属于当前用户 2查询时写的表名,并不是真正意义的表名,可能指向其他用户,或者就不是这个表

  9. thinkphp 找数据库某个字段为空的数据,PHP 数据调取 空数据

    $arr['dingwei'] = array('EXP','is null');

  10. Lua学习----零碎知识点

    Jit(just in time) 动态即时编译,边运行时边编译---->lua (主要是面向进程) Aot(ahead of time) 静态提前编译,运行前编译---->C#(主要是面 ...