首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

TreeSet Iterator 遍歷中刪除元素引發 ConcurrentModificationException 解析

     編輯:程式設計綜合問答

問題現場

TreeSet<String> set = new TreeSet<>();
set.add("one1");
set.add("two1");
set.add("three1");

for (Iterator<String> it = set.iterator(); it.hasNext(); ) {
    String str = it.next();
    System.out.println(str);
    if (str.equals("three1")) {
        set.remove(str); // 直接操作集合刪除
    }
}

執行結果拋出 ConcurrentModificationException,但書上說「遍歷到最後一個元素時操作集合不會拋異常」,為何矛盾?

根因深度剖析

1. TreeSet 內部機制

  • 底層由 TreeMap 實現,元素按 自然順序 或 Comparator 排序。
  • String 實作 Comparable,比較規則:"one1" < "three1" < "two1"(字典序)。
  • 因此迭代順序為:one1 → three1 → two1。

2. Fail-Fast 機制

Iterator 內部維護 expectedModCount,初始化時等於集合的 modCount。每次 next() 前會檢查:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

當你呼叫 set.remove() 而非 it.remove() 時,modCount 增加,但 expectedModCount 未同步更新,導致下一次 next() 或 hasNext() 觸發異常。

3. 為何書上說「最後一個元素不拋異常」?

這句話的前提是:刪除的是最後一個元素,且刪除後不再呼叫迭代器的任何方法。
你的案例中,刪除的是中間元素 three1,迭代器還需繼續取 two1,故必拋異常。

正確刪除姿勢

方案一:使用 Iterator 自帶的 remove()(推薦)

if (str.equals("three1")) {
    it.remove(); // 安全刪除,會同步 expectedModCount
}

方案二:收集待刪除元素,遍歷結束後批次刪除

List<String> toRemove = new ArrayList<>();
for (String s : set) {
    if (s.equals("three1")) toRemove.add(s);
}
set.removeAll(toRemove);

方案三:Java 8+ removeIf()

set.removeIf(s -> s.equals("three1"));

最佳實踐總結

  1. 遍歷過程中絕不直接操作集合(add/remove/clear),改用 Iterator.remove() 或事後批次處理。
  2. 若需高併發場景,考慮 CopyOnWriteArraySet 或加鎖。
  3. 理解 modCount 機制,是 Java 集合框架面試必考點。

掌握這些細節,能避開 90% 的集合遍歷坑,寫出更健壯的程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved