對JAVA集合進行遍歷刪除時務必要用迭代器


java集合遍歷刪除的方法:

1、當然這種情況也是容易解決,實現方式就是講遍歷與移除操作分離,即在遍歷的過程中,將需要移除的數據存放在另外一個集合當中,遍歷結束之后,統一移除。

2、使用Iterator遍歷刪除。

 

使用Iterator遍歷刪除的原因:

Iterator 是工作在一個獨立的線程中,並且擁有一個 mutex 鎖。 Iterator 被創建之后會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指針往后移動的時候就找不到要迭代的對象,所以按照 fail-fast 原則 Iterator 會馬上拋出 java.util.ConcurrentModificationException 異常。
所以 Iterator 在工作的時候是不允許被迭代的對象被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除對象, Iterator.remove() 方法會在刪除當前迭代對象的同時維護索引的一致性。

 

今天同事寫了幾行類似這樣的代碼:

public static void main(String args[]) {
    List<String> famous = new ArrayList<String>();
    famous.add("liudehua");
    famous.add("madehua");
    famous.add("liushishi");
    famous.add("tangwei");
    for (String s : famous) {
        if (s.equals("madehua")) {
            famous.remove(s);
        }
    }
}

運行出異常:

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

at java.util.AbstractList$Itr.next(AbstractList.java:343)

at com.bes.Test.main(Test.java:15)

Java新手最容易犯的錯誤,對JAVA集合進行遍歷刪除時務必要用迭代器。切記。

其實對於如上for循環,運行過程中還是轉換成了如下代碼:

for(Iterator<String> it = famous.iterator();it.hasNext();){
         String s = it.next();
         if(s.equals("madehua")){
             famous.remove(s);
         }
}

仍然采用的是迭代器,但刪除操作卻用了錯誤的方法。如將famous.remove(s)改成it.remove()

則運行正常,結果也無誤。

當然如果改成:

for (int i = 0; i < famous.size(); i++) {
            String s = famous.get(i);
            if (s.equals("madehua")) {
                famous.remove(s);
            }
}

這種方法,也是可以完成功能,但一般也不這么寫。

為什么用了迭代碼器就不能采用famous.remove(s)操作? 這種因為ArrayList與Iterator混合使用時會導致各自的狀態出現不一樣,最終出現異常。

我們看一下ArrayList中的Iterator實現:

 

private class Itr implements Iterator<E> {
   /**
    * Index of element to be returned by subsequent call to next.
    */
   int cursor = 0;
   /**
    * Index of element returned by most recent call to next or
    * previous.  Reset to -1 if this element is deleted by a call
    * to remove.
    */
   int lastRet = -1;
   /**
    * The modCount value that the iterator believes that the backing
    * List should have.  If this expectation is violated, the iterator
    * has detected concurrent modification.
    */
   int expectedModCount = modCount;
   public boolean hasNext() {
           return cursor != size();
   }
   public E next() {
           checkForComodification();
       try {
       E next = get(cursor);
       lastRet = cursor++;
       return next;
       } catch (IndexOutOfBoundsException e) {
       checkForComodification();
       throw new NoSuchElementException();
       }
   }
   public void remove() {
       if (lastRet == -1)
       throw new IllegalStateException();
           checkForComodification();
       try {
       AbstractList.this.remove(lastRet);
       if (lastRet < cursor)
           cursor--;
       lastRet = -1;
       expectedModCount = modCount;
       } catch (IndexOutOfBoundsException e) {
       throw new ConcurrentModificationException();
       }
   }
   final void checkForComodification() {
       if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
   }
}

基本上ArrayList采用size屬性來維護自已的狀態,而Iterator采用cursor來來維護自已的狀態。

當size出現變化時,cursor並不一定能夠得到同步,除非這種變化是Iterator主動導致的。

從上面的代碼可以看到當Iterator.remove方法導致ArrayList列表發生變化時,他會更新cursor來同步這一變化。但其他方式導致的ArrayList變化,Iterator是無法感知的。ArrayList自然也不會主動通知Iterator們,那將是一個繁重的工作。Iterator到底還是做了努力:為了防止狀態不一致可能引發的無法設想的后果,Iterator會經常做checkForComodification檢查,以防有變。如果有變,則以異常拋出,所以就出現了上面的異常。

 

在java常用的集合框架就是list ,set ,map 。

      list 可通過下標進行遍歷,set,map不能通過下表進行遍歷,因此對於set ,map的數據遍歷時,常常采用迭代器,不過在使用迭代器移除數據時存在陷阱。

      執行如下代碼:

        Set set = new HashSet();
        set.add(1);
        set.add(2);
        set.add(3);
        Iterator i = set.iterator();
        while(i.hashNext()){
            Object o = i.next();
            set.remove(o);    
        }

執行結果會出現以下異常:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)

這個問題在集合框架使用迭代器遍歷數據時存在,在java5新曾的foreach遍歷方式下也存在。在通過下表遍歷list的過程中不存在這個問題。

如:

 List list = new ArrayList();
 list .add(1);
 list.add(2);
 list.add(3);
 for(int i = 0 ; i < list.size();i++){
    list.remove(list.get(i));|| list.remove(i);
 }
//或者使用iterator的remove方法

代碼能夠正常執行。

利用java迭代器Itetator遍歷並刪除HashMap中的元素問題

問題:
下面的代碼試圖利用HashMap的Iterator對象遍歷該HashMap並刪除滿足條件的元素(比如超時的元素),但會拋出java.util.ConcurrentModificationException異常

    public static void main(String[] args)
    {      
        HashMap<String, String> hs=new HashMap();    
        hs.put("p1", "1");
        hs.put("p2", "1");
        hs.put("p3", "1");
        hs.put("p4", "1");
        hs.put("p5", "1");
        hs.put("p6", "1");       
        Iterator it=hs.keySet().iterator();      
        while(it.hasNext())
        {
            String str=(String)it.next();               
            System.out.println(hs);   
             //邏輯處理.........         
            hs.remove(str);      
        }   
    }

    原因應該是hs.remove(str)后,it內容沒變,並且it里的指針列表需要重新排序,所以只要確保刪除任一元素后,it保持同步更新即可:
    解決方案一:刪除任一元素后,it保持同步更新
   

            Iterator it = hs.keySet().iterator();
            while (it.hasNext()) {
            it = hs.keySet().iterator(); //重新排序
            String str = (String) it.next();
            System.out.println(hs);

            // 邏輯處理.........
            // .............
            hs.remove(str);
        }
        // ...........        

    這樣的時間復雜度明顯太大(兩層循環嵌套)
    解決方案二:由於刪除元素時,hs的iterator對象也重新排序,所以只要用hs的一個副本hsBack
Uackp的iterator去遍歷hs即可,這樣在刪除hs元素時iterator就不會重排了(因為刪除的是hs的元素,而不是該iterator所屬的hsBackUackp)

       //...................
        Map hsBackUp = (HashMap<String, String>)hs.clone();
        Iterator it = hsBackUp.keySet().iterator();
        System.out.println(hsBackUp);
        while(it.hasNext())
        {
            String str=(String)it.next();               
            System.out.println(hs);               
            hs.remove(str);       
        }   
        //.....................

    這樣雖然時間復雜度小了(只有一層循環),可是空間復雜度大了(多了一個hashmap的拷貝);
    查閱api文檔和相關資料后,原來iterator對象有一remove方法:

void remove() 
Removes from the underlying collection the last element returned by the
iterator (optional operation). This method can be called only once per
call to next
. The behavior of an iterator is unspecified if
the underlying collection is modified while the iteration is in
progress in any way other than by calling this method.
於是有下面的改進:
解決方案三:使用迭代器刪除
//..............................
Iterator it=hs.keySet().iterator(); 
while(it.hasNext())
{
    String str=(String)it.next(); 
    System.out.println(hs); 
    it.remove(); 
} 
//..............................

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM