首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 遍歷 HashMap 七種方式全解析:效能、順序、線程安全與 Java 8+ 新 API

     編輯:Java 相關知識

HashMap 是 Java 最高頻使用的集合,遍歷方式卻暗藏效能陷阱。本文對比七種遍歷寫法,從位元碼、JIT 內聯、記憶體分配、線程安全四個維度深度剖析,給出生產環境選型建議。

七種遍歷方式代碼對照


Map map = new HashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);

// 1. EntrySet + for-each (推薦通用)
for (Map.Entry e : map.entrySet()) { ... }

// 2. KeySet + for-each + get (兩次查找)
for (String k : map.keySet()) { Integer v = map.get(k); ... }

// 3. Iterator + EntrySet (可刪除)
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) { Map.Entry e = it.next(); if (...) it.remove(); }

// 4. forEach + Lambda (Java 8+)
map.forEach((k, v) -> { ... });

// 5. Stream API (函數式、可並行)
map.entrySet().stream().forEach(e -> { ... });
map.entrySet().parallelStream().forEach(e -> { ... }); // 並行需注意線程安全

// 6. Values 只遍歷值
for (Integer v : map.values()) { ... }

// 7. 傳統 for + EntrySet.toArray() (極少用)
Map.Entry[] entries = map.entrySet().toArray(new Map.Entry[0]);
for (Map.Entry e : entries) { ... }

效能與記憶體深度對比

方式位元碼特徵記憶體分配JIT 內聯適用場景
1. EntrySet for-eachiterator() + hasNext/next每次迭代分配 Map.Entry 節點引用 (極小)✅ 極易內聯**通用首選**,可讀性最佳
2. KeySet + get兩次 getNode 查找同 1,但 **雙倍哈希計算與桶定位**✅❌ 避免,除非極簡單邏輯
3. Iterator 顯式同 1,可呼叫 remove()同 1✅遍歷中需**安全刪除**
4. forEach LambdaforEachRemaining + Lambda 實例Lambda 實例捕獲變數可能分配✅ (Java 8+)簡單副作用邏輯,代碼緊湊
5. Stream建立 Stream 管道、Spliterator額外 Stream/Spliterator 對象部分內聯需**鏈式轉換/過濾/聚合/並行**
6. Values同 1,無 Key同 1✅只需值,不需 Key
7. toArray()一次性複製整個 EntrySet 到陣列O(n) 額外陣列記憶體✅需**快照隔離並發修改**

線程安全陷阱與解決方案

場景問題解決
單執行緒遍歷中修改ConcurrentModificationException (fail-fast)用 Iterator.remove() 或收集待刪 Key 後二次刪除
多執行緒並發讀寫數據不一致、死循環 (JDK 7)、CMEConcurrentHashMap (弱一致性迭代器)、Collections.synchronizedMap (鎖全圖)
並行 Stream 遍歷共享可變狀態競態條件、結果錯誤無狀態 Lambda、ConcurrentHashMap、Collectors.toConcurrentMap

Java 8+ 新 API 實用模式


// 過濾 + 轉換 + 收集
Map filtered = map.entrySet().stream()
    .filter(e -> e.getValue() > 10)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// 分組統計
Map freq = map.values().stream()
    .collect(Collectors.groupingBy(v -> v, Collectors.counting()));

// 並行聚合 (大數據量)
long sum = map.values().parallelStream().mapToInt(Integer::intValue).sum();

// forEachOrdered 保證順序 (LinkedHashMap)
map.forEachOrdered((k, v) -> System.out.println(k + "=" + v));

選型決策樹

  1. 只需值 → values() for-each
  2. 需 Key+Value、單執行緒、不刪除 → entrySet() for-each (方式 1)
  3. 遍歷中需安全刪除 → Iterator 顯式 (方式 3)
  4. 需鏈式過濾/轉換/聚合 → Stream API (方式 5)
  5. 大數據量可並行、無副作用 → parallelStream()
  6. 需快照隔離並發修改 → toArray() (方式 7) 或 ConcurrentHashMap
  7. 簡單副作用、追求代碼簡潔 → forEach Lambda (方式 4)

避坑清單

  • ❌ keySet() + get() — 雙倍查找開銷,嚴禁熱路徑使用
  • ❌ 並行 Stream 處理非線程安全 Map — 改用 ConcurrentHashMap 或串行
  • ❌ 遍歷中直接 map.remove(key) — 必拋 CME
  • ✅ 熱路徑遍歷大 Map 考慮 forEach 避免 Iterator 對象分配 (JIT 會標量替換)
  • ✅ LinkedHashMap 保持插入/訪問順序,配合 forEachOrdered

結語

遍歷 HashMap 看似微末,實則折射出「迭代器模式 vs 函數式管道、快照隔離 vs 弱一致性、串行 vs 並行」的工程權衡。掌握七種方式的底層機制與適用邊界,能讓你在代碼審查時一眼識別效能隱患,在高併發場景下寫出正確且快的遍歷邏輯。

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