zoukankan      html  css  js  c++  java
  • List迭代过滤操作注意点

    今天在写一段很简单的代码,本来以为肯定没什么问题,然后直接跑的时候,吆,简单的一个List的操作报错了。仔细一看代码,确实有问题,但是一般真的是如果稍微不小心就会犯下面这种愚蠢的操作。
    这里我把代码贴出来:
    public static void main(String[] args)
    	{
    		List<Integer> list = new ArrayList<>(1);
    		list.add(1);
    		for (Integer a : list)
    		{
    			if (a == 1)
    			{
    				list.remove(a);
    			}
    		}
    		list.forEach(System.out::println);
    	}
    上面的代码报错,我贴错误出来:



    然后第一时间觉得有问题,这种遍历然后删除的操作应该要使用迭代器。然后我修改后改成了下面代码:
    public static void main(String[] args)
    	{
    		List<Integer> list = new ArrayList<>(1);
    		list.add(1);
    		Iterator<Integer> iterator = list.iterator();
    		while (iterator.hasNext())
    		{
    			Integer next = iterator.next();
    			if (next == 1)
    			{
    				list.remove(next);
    			}
    		}
    		list.forEach(System.out::println);
    	}
    

    结果一运行同样报错,哎吆,一不小心还是直接去删除List了,然后再次修改才没了问题。最后修改的代码如下:
    public static void main(String[] args)
    	{
    		List<Integer> list = new ArrayList<>();
    		list.add(1);
    		Iterator<Integer> iterator = list.iterator();
    		while (iterator.hasNext())
    		{
    			Integer next = iterator.next();
    			if (next == 1)
    			{
    				iterator.remove();
    			}
    		}
    		list.forEach(System.out::println);
    	}


    所以决心好好的研究下这个List在迭代过程中的删减操作为什么很容易报错。打开前面2次报错的代码异常出现的地方,可以清楚的看见报错的原因。
     
    final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    modCount,expectedModCount这2个变量是什么搞不懂,所以还是好好研究下吧。在这里我们看那段用迭代器遍历然后直接删除list中一个对象的那段代码,我们来研究一下:
    打开JDK源码看一下Arraylist的add和remove操作:
    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    
    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
        }

     前面我们已经知道,不管是使用fore循环还是说使用迭代器,List内部操作的都是hasnext()和next()方法。这里贴出源码:
    private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
    
            public boolean hasNext() {
                return cursor != size;
            }
    
    
            @SuppressWarnings("unchecked")
            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];
            }
    
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
    
            @Override
            @SuppressWarnings("unchecked")
            public void forEachRemaining(Consumer<? super E> consumer) {
                Objects.requireNonNull(consumer);
                final int size = ArrayList.this.size;
                int i = cursor;
                if (i >= size) {
                    return;
                }
                final Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length) {
                    throw new ConcurrentModificationException();
                }
                while (i != size && modCount == expectedModCount) {
                    consumer.accept((E) elementData[i++]);
                }
                // update once at end of iteration to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
    
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

    上面的源码人家JDK里面的注释写的已经很清楚了,这里我们来整理一下:
    cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
    lastRet:表示上一个访问的元素的索引
    expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。
    modCount:AbstractList类中的一个成员变量,默认是0。
    该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。
    所以我们在对List做迭代操作的过程中,如果这个时候来添加或者删除这个List,这个时候expectedModCount是原来的初始化时候的modCount值,但是modCount这个时候都自增改变了值了,所以肯定就报错了。具体如下图:


    OK,现在这个时候我们已经知道了报错的原因了,那么为什么在直接使用interator迭代器来删除就没问题呢?
    我们自己看一下Iterator源码里面的remove()方法,就明白了。
    public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    //下面这行是亮点,重新设值expectedModCount了。
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }

    好了,现在错误原因已经清楚了,和公司CTO聊了下这个问题,人家的原话是这么说的:

    操作List在迭代的时候最好只是单纯的迭代,而不要试图去影响原来的那个List,特别是原来那个List的size长度。如果有需要最好也new一个新的list来处理过滤出来的数据最好。而且如果是单纯的要做查询就用Arraylist,如果要做插入和删除操作,最好用likenList。

    我个人觉得说的很好很正确,以后编码时候如果要做过滤一个List这种操作时候,最好新new一个容器来搬数据,不要试图直接操作原来那个List。


  • 相关阅读:
    HIVE Group by、join、distinct等实现原理
    【1.3】shell基础——调试shell(sh -x)
    sql server无法显示请求的对话框,检索数据失败
    sql server索引操作
    sql server中的alter
    tempdb无法收缩。使用 DBCC FREESYSTEMCACHE 解决
    在从该备份集进行读取时,RESTORE 检测到在数据库 "CISDB" 中的页(0:0)上存在错误。系统断定检查已失败
    【1.2】shell基础——stty erase解决按backspace出现^H的情况
    【1.1】shell基本实践——密码输入三次错误则结束
    (5.3.7)数据库迁移——sql server备份文件的加密解密
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232899.html
Copyright © 2011-2022 走看看