zoukankan      html  css  js  c++  java
  • ArrayList在foreach正常迭代删除不报错的原因

    一、背景

    在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题

    按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java.util.ConcurrentModificationException

    但是下面这段代码实际情况却没报异常,是什么情况?

    1     List<String> list = new ArrayList<String>();
    2         list.add("1");
    3         list.add("2");
    4         for (String item : list) {
    5             System.out.println("item:" + item);
    6             if ("1".equals(item)) {
    7                 list.remove(item);
    8             }
    9         }

    二、分析

    我们知道ArrayList的foreach迭代调用的是ArrayList内部类Itr,Itr源码如下:

     1     private class Itr implements Iterator<E> {
     2         int cursor;       // index of next element to return
     3         int lastRet = -1; // index of last element returned; -1 if no such
     4         int expectedModCount = modCount;
     5 
     6         public boolean hasNext() {
     7             return cursor != size;
     8         }
     9 
    10         @SuppressWarnings("unchecked")
    11         public E next() {
    12             checkForComodification();
    13             int i = cursor;
    14             if (i >= size)
    15                 throw new NoSuchElementException();
    16             Object[] elementData = ArrayList.this.elementData;
    17             if (i >= elementData.length)
    18                 throw new ConcurrentModificationException();
    19             cursor = i + 1;
    20             return (E) elementData[lastRet = i];
    21         }
    22 
    23         public void remove() {
    24             if (lastRet < 0)
    25                 throw new IllegalStateException();
    26             checkForComodification();
    27 
    28             try {
    29                 ArrayList.this.remove(lastRet);
    30                 cursor = lastRet;
    31                 lastRet = -1;
    32                 expectedModCount = modCount;
    33             } catch (IndexOutOfBoundsException ex) {
    34                 throw new ConcurrentModificationException();
    35             }
    36         }
    37 
    38         final void checkForComodification() {
    39             if (modCount != expectedModCount)
    40                 throw new ConcurrentModificationException();
    41         }
    42     }

    按照调用顺序查看源码

    1.hasNext

    ArrayList的foreach迭代首先调用的是ArrayList内部类ItrhasNext(),该方法会对当前循环指针和长度做判断。只有当hasNext()返回true才会执行foreach里面的代码,cursor = size时候就退出循环(因为这两者相等意味着都遍历完了,假如ArrayList的size=2,那么hasNext会被调用3次,但是第3次调用不会执行foreach里面代码)。

    2.如果上一步返回true的话会执行Itr的next(),如果数据无异常的话 cursor = i + 1;所以没执行一次next()时cursor都会+1

    3.接着会执行到 list.remove(item),此处调用的是ArrayList的remove(Object o)而不是Itr的,看下ArrayList的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     }

    o != null,会进入else的fastRemove(index);可以看到ArrayList根据传入的值删除会进行遍历equals判断,找到索引再通过fastRemove(index)删除,因此List频繁做删除修改效率比较低。

    4.再看下fastRemove()源码:

    1    */
    2     private void fastRemove(int index) {
    3         modCount++;
    4         int numMoved = size - index - 1;
    5         if (numMoved > 0)
    6             System.arraycopy(elementData, index+1, elementData, index,
    7                              numMoved);
    8         elementData[--size] = null; // clear to let GC do its work
    9     }

    第8行会把该索引对应的数组的值置为null,并且size-1。但是却没有进行 cursor - 1操作

    至此明白了。此处的ArrayList通过foreach迭代删除为什么不会报错:

    刚开始ArrayList的size=2时,cursor =0

    ①第一次hasNext()进来,cursor != size进入next()后cursor=1,接着因为满足条件删除的时候size-1=1;

    ②第二次hasNext()进来,cursor = size = 1,所以不会执行foreach的代码,也不会出现后面检测modCount值抛ConcurrentModificationException

    上述未抛异常的情况主要是hasNext()中判断遍历完成的条件与ArrayList删除后的数据刚好吻合而已。

    所以只要满足条件:删除的元素在循环时的指针cursor+1=size就会出现这种情况!删除ArrayList倒数第二个(即第 size - 1个元素)就会出现不抛异常的假象。

    (例如size=3,删除第2个元素;size=4,删除第3个元素)

    因为删除后size-1=cursor

     public boolean hasNext() {

    return cursor != size;

    }

    hasNext()会返回false,不会进入ArrayList的迭代器,就不会进入 next() 执行checkForComodification()

    这是一种条件判断下的特殊情况,其他情况都会抛出异常,所以不要在foreach进行删除数据。请在迭代器中进行删除。

  • 相关阅读:
    DockerCompose安装与快速体验
    Nginx:Docker部署与负载均衡开发实践
    JAVA基础:反射基础
    JVM:类加载机制
    JAVA基础:注解应用示例
    JAVA基础:注解机制
    JAVA并发(五):关键词final
    JAVA并发(四):关键词volatile
    Linux虚拟机配置作为旁挂路由器
    Linux起不来,如何修复
  • 原文地址:https://www.cnblogs.com/hupu-jr/p/8082187.html
Copyright © 2011-2022 走看看