zoukankan      html  css  js  c++  java
  • 动态删除集合遇到的一些问题理解

      这篇文章主要介绍在循环中动态删除集合(数组)元素遇到的问题:结果与实际期望的不符。待会看个例子,以及产生这个问题的原因和解决办法。

      实例场景一:

    public class Test {
        public static void printList(List list) {
            for(int i=0;i<list.size();i++) {
                System.out.println(list.get(i));
            }
        }
        public static void main(String[] args) {
            List<Boolean> list = new ArrayList();
            list.add(new Boolean(true));
            list.add(new Boolean(false));
            list.add(new Boolean(false));
            list.add(new Boolean(false));
            for(int i=0;i<list.size();i++) {
                if(list.get(i)) {
                    list.remove(i);
                }
            }
            printList(list);
        }
    }
    //output:
    //false
    //false
    //false

       这上面这个例子里,我们对判断list集合中的元素如果为true,就删掉这个元素。这时集合中只有第一个元素为true,所以删了它还有3个false元素,结果如我们所预想,接着对上面的list添加元素做些改变,在看看结果:

      

    public class Test {
        public static void printList(List list) {
            for(int i=0;i<list.size();i++) {
                System.out.println(list.get(i));
            }
        }
        public static void main(String[] args) {
            List<Boolean> list = new ArrayList();
            list.add(new Boolean(true));
            list.add(new Boolean(true));//仅在这里做了处理
            list.add(new Boolean(false));
            list.add(new Boolean(false));
            for(int i=0;i<list.size();i++) {
                if(list.get(i)) {
                    list.remove(i);
                }
            }
            printList(list);
        }
    }
    //output:
    //true
    //false
    //false

      这一段代码更上面相比,仅仅将list集合中index = 1的false改成了true,照理说这一点小改动无伤大雅,但输出结果却与我们期盼的不一致:为什么不是false,false?为什么角标为0、1号的元素只删了一个,而不是全删呢?我们对循环过程进行断点调试,结果就一目了然了:由于角标为0的元素为true,所以它首当其冲的要被删掉,这一点没什么疑虑,但由于0号位元素被删除,导致list.size()由4变成了3,此时的list为(true,false,false)。在第二轮循环体中,i已经自加完毕,值变成了1,所以list.gei(1)访问的(true,false,false)中的第二个元素,第一个true被直接跳过去了,导致它没被判断删除,捡回了一命。而后面的循环又奈我(false)何,而这个循环只循环了3次。问题已经分析出来了,现在怎么解决这个问题呢?难道万能经典的for循环解决不了这个问题吗?要知道我对它情有独钟啊!好吧,我们分析解决问题思路:首先在之前的for循序中,每删除一个元素,list.size()就减1,但进入下轮循环时,i又已经自增了一个1,这样下去势必导致循环次数的较少,我们的目的是不管他是否删除了元素,他都要循环最原始我们想循环的次数(4)。于是在这里设下判断:当要删除集合元素时(list.size()-1),i就不自增,当不删除集合元素时,i才自增;这样就可以控制循环的次数更最原始的循环次数一致了:

             

    public class Test {
        public static void printList(List list) {
            for(int i=0;i<list.size();i++) {
                System.out.println(list.get(i));
            }
        }
        public static void main(String[] args) {
            List<Boolean> list = new ArrayList();
            list.add(new Boolean(true));
            list.add(new Boolean(true));//仅在这里做了处理
            list.add(new Boolean(false));
            list.add(new Boolean(false));
            for(int i=0;i<list.size();) {
                if(list.get(i)) {
                    list.remove(i);
                }else {
                    i++;
                }
            }
            /*第二种写法
            for(int i=0;i<list.size();) {
                if(list.get(i)) {
                    list.remove(i);
                    continue;
                }
                i++;
            }*/
            
            printList(list);
        }
    }
    //output:
    //false
    //false

             通过这种写法,可以把{true,true,false,false}的第二个true的判断给补上去。 结果也就恢复正常了,看来写法丰富的for循环还是可以解决不少问题的。有时你遇到这种问题,想着换这一种遍历方式是不是就能避免呢,例如用迭代器(iterator).

    public static void main(String[] args) {
            List<Boolean> list = new ArrayList();
            list.add(new Boolean(true));
            list.add(new Boolean(true));//仅在这里做了处理
            list.add(new Boolean(false));
            list.add(new Boolean(false));
            Iterator<Boolean> it = list.iterator();
            while(it.hasNext()) {
                if(it.next()) {
                    it.remove();
                }
            }
            printList(list);
        }

    输出结果也是false,false。从这可以看出,迭代器帮我们解决了刚刚遇到的问题。而且它的写法更简单。看来我们又得庆幸多了一条解决之道。但在庆幸的同时,是否会好奇迭代器是怎么帮我们解决的呢?反正我闲的蛋疼,抱着能看懂多少算多少的态度去分析了源码,在此向大家汇报一下:

      1.Iterator是一个接口,由于我们这里的list实际上是一个ArrayList,那我们就直接在ArrayList.class这里找,一下是类里面我们用到的几个方法:

    public Iterator<E> iterator() {
            return new Itr();
        }
    
        /**
         * 能理解的就写注释,不能理解的不理会了,请原谅我太菜了。。
         */
        private class Itr implements Iterator<E> {
            int cursor;       // 返回下一个元素的索引
            int lastRet = -1; // 当前正在操作的元素的索引
            int expectedModCount = modCount;
    
            Itr() {}
            //调这个方法时,注意cursor与lastRet值都没有变化,可以理解游标压根就不移动,底下的next,remove()
            //才去改变这两个值,不更改集合的情况下:cursor初始为0,每次move()后,cursor加1,move()四次后,cursor=4,
            //所以第五次进去while()循环判断,返回false。
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();
                int i = cursor;
                //先判断hasNext(),再进入这个next(),底下这两个判断成立的情况下,hasNext()都会返回false,
                //所以在hasNext()= true时,这两个if是不会进去的。
                if (i >= size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                //从这里可以看出每次move,cursor+1,此时cursor表示的是下一个元素的索引,所以它的值先行加了1,
                //而我们要取的是集合中索引为0的元素,也就是lastRet = cursor(这个cursor是还未加1之前的值,这个很重要)=0
                cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    
                    //从实例理解:在我们的例子中要删除集合索引为1的元素,此时lastRet=1,删除后,我们将要操作的下一个元素
                    //索引cursor 赋值为 1;从而保证了下一次调next()方法时lastRet = 1(看上一行注释括号里的内容).因此当集合中只有3个元素时
                    //它还是从第二个元素操作起。跟我们for循环时,如果删除元素,那一次循环i就不自增,达到同一个效果。
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
           
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

                    迭代器的实现过程在注释写明了,大家可以看看。至此,先前的问题也算水落石出了。还有一点值得一提:Java数组长度是不可变的,而js 里面数组长度是可以动态添加的,当你在动态删除js数组的内容时,也会遇到刚提到的问题,这是你可以考虑用上面提到的for循环写法来解决,毕竟这时Java提供的迭代器是帮不上忙的。                                                                                                                                                       

  • 相关阅读:
    PHP基本的语法以及和Java的差别
    Linux 性能測试工具
    【Oracle 集群】Linux下Oracle RAC集群搭建之Oracle DataBase安装(八)
    【Oracle 集群】Oracle 11G RAC教程之集群安装(七)
    【Oracle 集群】11G RAC 知识图文详细教程之RAC在LINUX上使用NFS安装前准备(六)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 特殊问题和实战经验(五)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之缓存融合技术和主要后台进程(四)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)
    Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之ORACLE集群概念和原理(二)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)
  • 原文地址:https://www.cnblogs.com/shu94/p/9571248.html
Copyright © 2011-2022 走看看