• 动态删除集合遇到的一些问题理解


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

      实例场景一:

    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提供的迭代器是帮不上忙的。                                                                                                                                                       

  • 相关阅读:
    应用程序是否能控制SDK的P2P连接?
    如何使用流媒体数据代理功能?
    怎么样设置AnyChat视频质量?
    AnyChat在打开音频设备的同时会自动发布视频数据吗?
    单层感知器--matlab神经网络
    Django Day1
    云知声Java实习面试(offer到手含面试经验及答案)
    第四范式Java实习面试(offer到手含面试经验及答案)
    悦畅科技Java实习面试(offer到手含面试经验及答案)
    花椒直播Java实习面试(offer到手含面试经验及答案)
  • 原文地址:https://www.cnblogs.com/shu94/p/9571248.html
Copyright © 2020-2023  润新知