2012-04-02 58 views
1

我試圖寫一個非常簡單的方法來刪除一個LinkedList重複:爲什麼在迭代器上調用remove()會給出ConcurrentModificationException?

我嘗試這樣做,而無需使用額外的緩衝,所以我保持鏈表上兩個迭代器,一個不正常的迭代和另一遍歷所有先前的節點來檢查dupe(如CareerCup中所示);然而,編譯器告訴我有一個CME即使我打電話itr1.remove():

public static void RemoveWithoutBuffer(LinkedList l) { 
    ListIterator itr1 = l.listIterator(); 
    int count1 = 0; 
    int count2 = 0; 
    while (itr1.hasNext()) { 
     Object next = itr1.next(); 
     count1++; 
     count2 = 0; 
     ListIterator itr2 = l.listIterator(); 
     while (itr2.hasNext()) { 
      count2++; 
      if (count2 == count1) 
       break; 
      if (itr2.next() == next){ 
       itr1.remove(); 
      } 
     } 

    } 
} 

這個問題用一個HashSet的援助的另一個更簡單的解決方案易於如下,也不例外報道:

public static void Remove(LinkedList l) { 
    HashSet set = new HashSet(); 
    ListIterator itr = l.listIterator(); 
    while (itr.hasNext()) { 
     Object next = itr.next(); 
     if (set.contains(next)) 
      itr.remove(); 
     else 
      set.add(next); 
    } 
} 

是因爲當我通過itr2迭代時,我無法修改itr1嗎?有沒有辦法來解決這個問題?感謝你們。

回答

3

在第一種情況下是 - 您正在通過iterator2更改集合的內容,而iterator1不知道這些更改。在第二種情況下,HashSet/HashMap不允許在迭代元素的同時移除元素。

您可以將已移除的元素添加到另一個集合,並在迭代後將它們移除。例如。

List toRemove = new ArrayList(); 
    for (Object next : collection) { 
     if (someCondition) toRemove.add(next); 
    } 
    collection.removeAll(toRemove); 

我希望它有幫助。

P.S.有關如何從列表中刪除元素的更多詳細信息,請參閱此處可以讀取的算法複雜度Removing ArrayList object issue

+0

謝謝。這個解決方案非常好,但仍然佔用一些額外的空間,但我相信它比使用hashSet節省更多的空間。感謝您的鏈接了。 – Superziyi 2012-04-02 03:44:30

+0

如果涉及空間,可以將元素標記爲已刪除(即對列表設置爲空,或將特殊鍵與「isRemoved」屬性集合在一起並稍後執行實際刪除)。 – 2012-04-02 03:48:50

0

您正在獲取CME,因爲該列表被第二個迭代器修改,並且第一個迭代器不知道該更改。當下次嘗試訪問列表時,它已被修改。因此它會拋出異常。

請一次只使用一個迭代器修改列表。

0

是的。你可以這樣想:當你創建一個迭代器時,它會得到列表當前的「修改計數」。當迭代器從列表中刪除一個元素時,它會檢查修改計數以查看它是否符合要求,如果一切正常,它將刪除元素並更新迭代器和列表上的修改計數。另一個迭代器將仍然具有舊的修改計數並查看新值並拋出CME。

基於哈希集的方式在大多數情況下是正確的解決方案。它會表現得更好 - 兩個O(n)通常比O(n^2)更好,因爲嵌套迭代解決方案會產生(如果它工作的話)。

+0

謝謝!所以我猜想沒有辦法真正使用2個迭代器來移除該項目而不訴諸額外的空間?尤金給出的解決方案非常棒,但仍然消耗一些額外的空間。是的,在時間上,這是不好的,只是想在不使用臨時緩衝區的情況下嘗試。 – Superziyi 2012-04-02 03:44:49

3

the API docs

此類的iteratorlistIterator 方法返回的迭代器是快速失敗的:如果列表在任何 時間從結構上修改創建迭代器之後,以任何方式除了通過 迭代器自己的removeadd方法,迭代器將拋出 ConcurrentModificationException

0

爲什麼你CME被別人解釋的原因,這裏是你可以用它來去除的DUP

使用列表toRemove在第一時間iterator跌入它來記錄元素,後來當滿足可能的方式再次使用已記錄的元素,將其刪除使用iterator.remove()

 
private void removeDups(List list) { 
     List toRemove = new ArrayList(); 
     for(Iterator it = list.iterator(); it.hasNext();) { 
      Object next = it.next(); 
      if(!toRemove.contains(next)) { 
       toRemove.add(next); 
      } else { 
       it.remove(); 
      } 
     } 
     toremove.clear(); 
    } 

0

是的,您可以在不使用額外空間的情況下修復此問題。

問題來自一個接一個地執行這兩條線。

  • itr1.remove();
  • itr2.hasNext();

您可以在同一個列表中同時使用兩個迭代器。 如果你小心
但是,一旦這些迭代器之一修改了列表(因爲它發生在itr1.remove()),您不能再使用其他迭代器(因此您不能使用itr2.hasNext())。

解決的辦法是在itr1.remove()之後加上break。並更新count1

public static void RemoveWithoutBuffer(LinkedList l) { 
    ListIterator itr1 = l.listIterator(); 
    int count1 = 0; 
    int count2 = 0; 
    while (itr1.hasNext()) { 
     Object next = itr1.next(); 
     count1++; 
     count2 = 0; 
     ListIterator itr2 = l.listIterator(); 
     while (itr2.hasNext()) { 
      count2++; 
      if (count2 == count1) 
       break; 
      if (itr2.next() == next){ 
       itr1.remove(); 
       --count1; 
       break; 
      } 
     } 
    } 
} 

一個更優雅的解決方案是與當前的一個,而不是元素當前之前之後的元素進行比較:

public static void RemoveWithoutBuffer(LinkedList l) { 
    ListIterator itr1 = l.listIterator(); 
    while (itr1.hasNext()) { 
    Object next = itr1.next(); 
    ListIterator itr2 = l.listIterator(itr1.nextIndex()); 
    while (itr2.hasNext()) { 
     if (itr2.next() == next) { 
     itr1.remove(); 
     break; 
     } 
    } 
    } 
} 

這些都不是在以下方面的最佳解決方案計算複雜度。如果內存空間不是問題,那麼關於計算複雜性,哈希集解決方案是更好的解決方案。

但問題是關於通過迭代器進行併發處理,而不是關於複雜度優化。

相關問題