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(); } //..............................
