ArrayList中元素的删除操作

在聊 ArrayList 的删除删除操作之前,先来说说它的遍历方法。

一个 list 的遍历方法主要有三种:

  • Iterator 迭代器遍历
  • 遍历下标 for 循环遍历
  • forEach 遍历

对于这三种遍历方法,产生的删除操作 (remove) 结果也会不一样。我们主要将 forEach 遍历删除会出现的问题。

我们先来看一段代码

public static void main(String[] args) {List<String> list1 = new ArrayList<>(3);list1.add("name");list1.add("age");list1.add("phone");for (String str : list1){if ("age".equals(str)){list1.remove(str);}}
}

这样子进行删除操作是不会出现问题的,然而当我们把删除的判断条件换成 phone 后就会报异常。

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.wiceflow.collection.List.ListRemove.main(ListRemove.java:18)

分析源码揭开它的秘密

我们知道,forEach 循环其实是走 list 的迭代器进行遍历的,我们先看 ArrayList 内部的 forEach 方法。

ArrayList 中有一个内部类 Itr 实现了 Iterator ,还有一个 ListItr 继承了 Itr这个类初始化的时候会将 ArrayList 对象的 modCount 属性的值赋值给 expectedModCount)。

先看迭代器的 next 方法

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];
}
/**
* 在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作
* 这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。
* 在AbstractList中,使用了一个简单的机制来规避这些风险。
* 这就是modCount和expectedModCount的作用所在
*/
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}

关于 checkForComodification() 方法更多的可以看这篇博文:
Java 集合中常见 checkForComodification()方法的作用

我们可以看到,list 每次获取下一个对象前都要去检查一下光标是否越界。在 ArrayList 的所有涉及结构变化的方法中都增加 modCount 的值,包括:add()、remove()、addAll()、removeRange()clear() 方法。这些方法每调用一次,modCount 的值就加 1。而变量 expectedModCount 在迭代开始时便会被赋值成 modCount 的值。所以在循环遍历中,改变结构变化的方法,例如 add()、remove() 都会是 modCount 增长 1 ,而 expectedModCount 却不会变化。

注意,以上讲的涉及到结构变化的方法是 ArrayList 的方法,不是其内部类 Itr 的方法。

来看一下 ArrayListremove 方法

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;
}  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
}

从上面源码中我们不难发现,ArrayList 中两个 remove() 方法都对 modCount 进行了自增,那么我们在用迭代器迭代的时候,若是删除 末尾 的元素,则会造成 modCountexpectedModCount 的不一致导致异常抛出。

为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常?

我们来看一下 ArrayList 中的内部类 Itr 。我们在调用迭代器的 Next() 方法之前会先调用 hasNext() 方法。

public boolean hasNext() {return cursor != size;
}

从代码上我们可以看出判断条件是当 cursor != size 的时候,才会进行下一次循环,而 cursor 参数是我们迭代循环的下标,在我们删除倒数第二个元素后,此时 list 的大小减了 1,再进入下一次循环后会出现 cursor == size ,也就是 hasNext() 便会返回 false 终止了循环。实际上 modCount 的数值也增加了 1,只不过循环没发执行到那里,所以异常也就不会被抛出来了。

for 下标遍历删除

从源码上我们可以看出,在利用 for 下标进行遍历的时候,并不会触发 checkForComodification() 方法,所以此时只要要删除的位置比列表大小小时都不会出错。

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;
}

ArrayList 源码介绍中,作者是推荐使用 for ( int i; i < size; i++) 方式去遍历,而不是 foreach 或者迭代,这个主要是因为 list 接口实现了 RandomAccess 接口。 实现这个接口的集合是随机无序的,所以遍历的时候一般使用上述的 for,记住一点就可以了所有实现了 RandomAccess 接口的集合都是用一般 for 就可以了(可以通过 api 查看那些集合实现了 RandomAccess)。

Iterator 迭代遍历删除

这里我们将的 Iterator 遍历删除调用的方法不是 ArrayListremove 方法,而是其内部类的 remove 方法

我们看源码不难发现,在 Itr 类中,属性 expectedModCount 在调用外部的 remove() 方法后再次被赋值,此时 expectedModCount 是等于 modCount 的。

public void remove() {if (lastRet < 0)throw new IllegalStateException();// 这里检查时候还没有进行删除操作checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;// 先进行了 remove 操作后 再重新对 expectedModCount 进行赋值expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}

所以在使用 Iterator 进行遍历删除时不会出现 ConcurrentModificationException 异常。

ArrayList中元素的删除操作相关推荐

  1. Python列表(获取列表中指定元素的索引、获取列表中的多个元素、判断指定元素是否在列表中存在、列表元素的遍历、列表元素的增加操作、 列表元素的删除操作、列表元素的修改操作、列表元素的排序操作)

    1.获取列表中指定元素的索引 eg1:未指定索引范围查找索引 zyr=['憨憨','憨宝'] print(zyr.index('憨宝')) print(zyr[1]) eg2:在指定索引范围内查找元素 ...

  2. 3.5链表----链表中元素的删除(只删除一个元素情况)

    3.5链表----链表中元素的删除(只删除一个元素情况) 该部分与上一节是息息相关的,关于如何在链表中删除元素,我们一步一步来分析: 一.图示删除逻辑 假设我们需要在链表中删除索引为2位置的元素,此时 ...

  3. java list 元素排序_对arraylist中元素进行排序实例代码

    rrayList中的元素进行排序,主要考查的是对util包中的Comparator接口和Collections类的使用. 实现Comparator接口必须实现compare方法,自己可以去看API帮助 ...

  4. elementui在table中使用el-popover删除操作

    elementui在table中使用el-popover删除操作 .vue <el-table-column label="操作" width="120" ...

  5. ArrayList中remove()方法删除元素之后下标重定位的问题

    需求: 有一个ArrayList数组,要求删除长度大于5的字符串,如:arr = {"ab1","123ad","bca","da ...

  6. map中的迭代删除操作注意问题

    如果map中包含若干个key为10的倍数,要删除key为10的倍数. 错误形式 #include <iostream> #include <map> #include < ...

  7. vector 中元素的删除

    vector 中删除元素的方法是:b.erase(it);  //b是vector, it 是 vector::iterator 但是删除vector 中的元素有些诡异(^_^),稍不注意,就会出错. ...

  8. vector中元素的删除

    如果想要删除vector中值为val的元素,最容易想到的方法就是对vector进行遍历,然后遇到值为val时就将其删除.比较好的是vector容器有erase操作. 但是这儿需要注意,与list中的e ...

  9. java list 获取索引_java – 获取arrayList中元素的索引

    我试图在arrayList minuteList中获得466的索引 [288, 318, 346, 376, 406, 436, 466, 1006, 1036, 1066, 1096, 1126, ...

最新文章

  1. Asp.net 序列化应用实例(转载)
  2. Spring MVC中获取当前项目的路径
  3. G2. 唐纳德与子串 (Hard)kmp
  4. linux 镜像自动安装,制作能自动安装的CentOS镜像文件
  5. 《四世同堂》金句摘抄(十七)
  6. hibernate 别名_Hibernate:在sqlRestriction上使用联接表别名
  7. Modify HTTP Headers (Examples)
  8. 1个平方大概多少立杆_1斤草坪种子播撒多少平方/四季青
  9. 【重点】LeetCode 143. Reorder List
  10. 学习STM32F769DK-OTA例程之APP中断向量表重映射
  11. DFA极简化和NFA确定化
  12. 白鹭发布html5,白鹭Egret Engine 1.5发布 HTML5性能大幅提升
  13. Luarocks: 安装 lyaml 库
  14. 拯救 中国区 谷歌翻译 解决方案
  15. Python爆力破解rar密码并对比多线程的效率
  16. 拒酒词,社交必备!!!
  17. 用Python去优惠券,看到结果我惊呆了!
  18. putty小键盘输入_putty或xshell上用vi/vim小键盘无法使用的解决方法-阿里云开发者社区...
  19. 英寸和厘米的交互python_matplotlib 设置图形大小时 figsize 与 dpi 的关系
  20. 关于正则表达式里含有空格的问题

热门文章

  1. 生于七八十年代,我们的童年是这样的
  2. 自走棋显示连接服务器就断了,赤潮自走棋无法连接服务器是什么原因
  3. oracle删除两个月以前的数据,Oracle恢复删除数据
  4. VS编译时一些常见错误积累LNK,比如LNK2019、LNK2001(实时更新)
  5. IDEA启动VUE项目的方法
  6. tftp命令使用详解
  7. 国内股票KDJ指标计算,Python实现KDJ指标计算,Talib实现KDJ指标计算
  8. Android textview设置ttf字体库本地库和网络库使用的两种方式
  9. AWS KVS(Kinesis Video Streams)之WebRTC移植编译(三)
  10. 南大科院大数据Hadoop工程实训