ArrayList在遍历时做删除的陷阱
来源:互联网 发布:淘宝大学在哪里看 编辑:程序博客网 时间:2024/06/06 01:51
Java中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下。
先写个测试代码:
- import java.util.ArrayList;
- public class ArrayListRemove {
- public static void main(String[] args) {
- ArrayList<String> list = new ArrayList<String>();
- list.add("a");
- list.add("bb");
- list.add("bb");
- list.add("ccc");
- list.add("ccc");
- list.add("ccc");
- remove(list);
- for (String s : list) {
- System.out.println("element : " + s);
- }
- }
- public static void remove(ArrayList<String> list) {
- // TODO:
- }
- }
错误写法示例一:
- public static void remove(ArrayList<String> list) {
- for (int i = 0; i < list.size(); i++) {
- String s = list.get(i);
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
这种最普通的循环写法执行后会发现有一个“bb”的字符串没有删掉。
错误写法示例二:
- public static void remove(ArrayList<String> list) {
- for (String s : list) {
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
这种for each写法会发现报出著名的并发修改异常java.util.ConcurrentModificationException。
要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:
- 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;
- }
按一般执行路径会走到else路径下最终调用faseRemove方法:
- 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; // Let gc do its work
- }
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。
针对这种情况可以倒序删除的方式来避免:
- public static void remove(ArrayList<String> list) {
- for (int i = list.size() - 1; i >= 0; i--) {
- String s = list.get(i);
- if (s.equals("bb")) {
- list.remove(s);
- }
- }
- }
因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。
而错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
- public Iterator<E> iterator() {
- return new Itr();
- }
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator<E>,看这个类的next方法:
- public E next() {
- checkForComodification();
- try {
- E next = get(cursor);
- lastRet = cursor++;
- return next;
- } catch (IndexOutOfBoundsException e) {
- checkForComodification();
- throw new NoSuchElementException();
- }
- }
第一行checkForComodification方法:
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
- public static void remove(ArrayList<String> list) {
- Iterator<String> it = list.iterator();
- while (it.hasNext()) {
- String s = it.next();
- if (s.equals("bb")) {
- it.remove();
- }
- }
- }
- ArrayList在遍历时做删除的陷阱
- LinkedList,ArrayList在foreach循环遍历时可以删除元素!!!
- java.util.ArrayList在foreach循环遍历时删除元素的问题
- ArrayList 遍历时删除(添加)常见陷阱及其解决以及fail-fast 机制
- 集合list在遍历时是不能做删除操作的
- 分析ArrayList在遍历时修改报错的原因
- Java ArrayList遍历时删除一个元素
- Java ArrayList遍历时删除元素
- java ArrayList遍历时删除元素
- Java ArrayList遍历时删除一个元素
- c++下list在遍历时删除节点的方法
- List遍历时陷阱
- [破除迷信]java.util.ArrayList在foreach循环遍历时可以删除元素
- [破除迷信]java.util.ArrayList在foreach循环遍历时可以删除元素
- 管理器的陷阱|列表遍历时修改了列表
- ArrayList遍历时不能写
- CCArray数组或者vector容器元素在遍历时删除的删除技巧
- c++中List在遍历时删除节点的方法,带源码
- Oracle之索引/视图/游标及PL/SQL编程
- 背景透明问题 rgba
- vs2013 vs2015 集成环境yasm 配置
- VS2013+OPENCV3.0.0配置纪要
- Scala 学习笔记(六)------Scala 函数嵌套
- ArrayList在遍历时做删除的陷阱
- 计算一个整数二进制位中1的个数
- window安装redis
- VF01 模拟开票
- 系统远程通讯Dubbo
- (递推入门)超级楼梯 -- HDOJ
- maven build工程报错 Perhaps you are running on a JRE rather than a JDK
- 关于Class.getResource和ClassLoader.getResource的路径问题
- git问题解决方法