ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下。

先写个测试代码:

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

错误写法示例一:

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

这种最普通的循环写法执行后会发现有一个“bb”的字符串没有删掉。

错误写法示例二:

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

这种for each写法会发现报出著名的并发修改异常java.util.ConcurrentModificationException。

要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (int index = 0; index < size; index++)
  4. if (elementData[index] == null) {
  5. fastRemove(index);
  6. return true;
  7. }
  8. } else {
  9. for (int index = 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. int numMoved = size - index - 1;
  4. if (numMoved > 0)
  5. System.arraycopy(elementData, index+1, elementData, index,
  6. numMoved);
  7. elementData[--size] = null; // Let gc do its work
  8. }

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

针对这种情况可以倒序删除的方式来避免:

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

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

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

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

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator<E>,看这个类的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的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

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

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

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

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

  2. ArrayList循环遍历并删除元素的常见陷阱

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

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

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

  4. Java中List循环遍历的时候删除当前对象(自己)

    方法一 Java代码   ArrayList<String> list = new ArrayList<String>();           list.add(" ...

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

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

  6. Java中ArrayList边遍历边修改

    用for-each 边遍历ArrayList 边修改时: public static void main(String[] args) { ArrayList<String> list = ...

  7. Java中for循环遍历List的两种方法

    我们平常使用的方法: List<WebElement> element = driver.findElements(By.tagName("input"));      ...

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

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

  9. 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错

    一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...

随机推荐

  1. BZOJ4870 [六省联考2017] 组合数问题 【快速幂】

    题目分析: 构造f[nk][r]表示题目中要求的东西.容易发现递推公式f[nk][r]=f[nk-1][r]+f[nk-1][(r-1)%k].矩阵快速幂可以优化,时间复杂度O(k^3logn). 代 ...

  2. Maven整理

    第一章 Maven安装 1.1 下载Maven库 下载地址:http://maven.apache.org/download.cgi 1.2 解压下载的库,认识Maven库目录 备注: 解压文件尽量不 ...

  3. (python爬取小故事网并写入mysql)

    前言: 这是一篇来自整理EVERNOTE的笔记所产生的小博客,实现功能主要为用广度优先算法爬取小故事网,爬满100个链接并写入mysql,虽然CS作为双学位已经修习了三年多了,但不仅理论知识一般,动手 ...

  4. 【BZOJ4815】[CQOI2017]小Q的表格(莫比乌斯反演,分块)

    [BZOJ4815][CQOI2017]小Q的表格(莫比乌斯反演,分块) 题面 BZOJ 洛谷 题解 神仙题啊. 首先\(f(a,b)=f(b,a)\)告诉我们矩阵只要算一半就好了. 接下来是\(b* ...

  5. 【BZOJ2001】[HNOI2010]城市建设(CDQ分治,线段树分治)

    [BZOJ2001][HNOI2010]城市建设(CDQ分治,线段树分治) 题面 BZOJ 洛谷 题解 好神仙啊这题.原来想做一直不会做(然而YCB神仙早就切了),今天来怒写一发. 很明显这个玩意换种 ...

  6. linux 分区、目录及用途

    主要分区: 目录 建议大小 格式 描述 / 10G-20G ext4 根目录 swap <2048M swap 交换空间 /boot 200M左右 ext4 Linux的内核及引导系统程序所需要 ...

  7. N皇后问题(DFS)

    题目:在N*N的国际象棋棋盘上放置N个皇后彼此不受攻击(即在棋盘的任一行,任一列和任意对角线上不能放置2个皇后),求解所有摆放方案的总数. 样例输入: 1 8 样例输出: 1 92 解题思路:由于皇后 ...

  8. A1031. Hello World for U

    Given any string of N (>=5) characters, you are asked to form the characters into the shape of U. ...

  9. Vector 是线程安全的,是不是在多线程下操作Vector就可以不用加Synchronized

    如标题一样,如果之前让我回答,我会说,是的,在多线程的环境下操作Vector,不需要加Synchronized. 但是我今天无意间看到一篇文章,我才发现我之前的想法是错误的,这篇文章的地址: http ...

  10. 有趣的filter

    js中的filter就是过滤的意思,比如,我们以什么样的方式进行过滤,得到我们想要的结果. 对,我们要的就是这个结果. 给定一个数组,我们要的是Burger(汉堡) const restaurants ...