HashMap 遍歷是 Java 開發的高頻操作,但很多開發者只會一兩種寫法,不清楚各種方式的適用場景與效能差異。本文系統梳理六大遍歷方式,從原理、語法、效能、適用場景全方位解析。
| 方式 | 核心 API | 適用場景 | 可修改 Map | 效能 |
|---|---|---|---|---|
| 1. keySet + for-each | map.keySet() | 只需 key,需同時取 value | 否 (拋 CME) | ★★★☆☆ |
| 2. values + for-each | map.values() | 只需 value | 否 | ★★★★☆ |
| 3. entrySet + for-each | map.entrySet() | 需 key-value 對,**最推薦** | 否 | ★★★★★ |
| 4. keySet + Iterator | map.keySet().iterator() | 遍歷中需刪除 entry | 是 (iterator.remove) | ★★★☆☆ |
| 5. values + Iterator | map.values().iterator() | 遍歷中需刪除 value | 是 | ★★★★☆ |
| 6. entrySet + Iterator | map.entrySet().iterator() | 遍歷中需刪除、或需 key-value 同時操作 | 是 | ★★★★★ |
Map map = new HashMap<>();
map.put("001", "劉備");
map.put("002", "曹操");
map.put("003", "孫權");
for (String key : map.keySet()) {
String value = map.get(key); // 額外查找
System.out.println("Key: " + key + ", Value: " + value);
}
// 缺點:每次 map.get(key) 額外哈希查找
for (String value : map.values()) {
System.out.println("Value: " + value);
}
// 優點:無額外查找,但無法獲取 key
for (Map.Entry entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 優點:單次迭代同時獲取 key-value,無額外查找,語義最清晰
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if ("002".equals(key)) {
it.remove(); // 安全刪除,不拋 ConcurrentModificationException
}
}
Iterator it = map.values().iterator();
while (it.hasNext()) {
String value = it.next();
if ("曹操".equals(value)) it.remove();
}
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
if ("003".equals(entry.getKey())) {
System.out.println("刪除:" + entry.getKey() + "=" + entry.getValue());
it.remove();
}
}
Map 代表「鍵值對映射」,而非單一元素序列。遍歷視角有三種:鍵集合、值集合、鍵值對集合。Map 提供三個視圖方法:
keySet() → Set 實作 Iterablevalues() → Collection 實作 IterableentrySet() → Set> 實作 IterableHashMap 內部維護 modCount,for-each 隱式建立的 Iterator 讀取初始 modCount,若遍歷中直接 map.remove() 導致 modCount 變化,下次迭代會拋 ConcurrentModificationException。但 Iterator.remove() 會同步更新 expectedModCount,故安全。
Benchmark Mode Cnt Score Error Units HashMapIteration.entrySetForEach thrpt 10 12.45 ± 0.32 ops/ms HashMapIteration.entrySetIterator thrpt 10 12.10 ± 0.28 ops/ms HashMapIteration.keySetForEach thrpt 10 10.80 ± 0.35 ops/ms HashMapIteration.valuesForEach thrpt 10 11.20 ± 0.30 ops/ms HashMapIteration.keySetIterator thrpt 10 10.50 ± 0.25 ops/ms HashMapIteration.valuesIterator thrpt 10 10.90 ± 0.27 ops/ms
結論:entrySet 系列最快,因避免額外 get() 查找。Iterator 版本略慢於 for-each (方法呼叫開銷),但差異微小。
// 1. BiConsumer (最簡潔)
map.forEach((k, v) -> System.out.println(k + "=" + v));
// 2. 串流處理 (可並行、過濾、轉換)
map.entrySet().stream()
.filter(e -> e.getKey().startsWith("00"))
.forEach(e -> System.out.println(e.getKey() + "=" + e.getValue()));
// 3. 並行處理大數據量
map.forEach((k, v) -> { /* 線程安全操作 */ }); // 注意:forEach 不保證順序
需要在遍歷中刪除?
├─ 是 → 必須用 Iterator (方式 4/5/6)
│ ├─ 只需 key 刪除 → keySet().iterator()
│ ├─ 只需 value 刪除 → values().iterator()
│ └─ 需 key-value 判斷刪除 → entrySet().iterator() ⭐
└─ 否 → 只讀遍歷
├─ 只需 value → values() for-each
├─ 只需 key → keySet() for-each (罕見)
└─ 需 key-value → entrySet() for-each ⭐⭐⭐
for (Entry e : map.entrySet()) 或 map.forEach((k,v)->...)Iterator> it = map.entrySet().iterator() for (V v : map.values())map.entrySet().parallelStream() (JDK 8+)LinkedHashMap 保證插入/訪問順序