1. 程式人生 > >動態刪除集合遇到的一些問題理解

動態刪除集合遇到的一些問題理解

而不是 遇到 str vat 返回 continue edm 除了 裏的

  這篇文章主要介紹在循環中動態刪除集合(數組)元素遇到的問題:結果與實際期望的不符。待會看個例子,以及產生這個問題的原因和解決辦法。

  實例場景一:

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

動態刪除集合遇到的一些問題理解