Map 不直接實作 Iterable,必須透過 values()、keySet()、entrySet() 取得集合視圖再遍歷。三種方式各有優劣,選錯可能導致效能損耗或程式碼冗餘。本文以 HashMap<String, Student> 為例,完整示範並對比。
Map<String, Student> map = new HashMap<>();
map.put("1001", new Student("宋江", "1001", 38));
map.put("1002", new Student("盧俊義", "1002", 35));
map.put("1003", new Student("吳用", "1003", 34));
map.putAll(Map.of("1008", new Student("tom", "1008", 12),
"1009", new Student("jerry", "1009", 10)));
public static void traverseValues(Map<String, Student> map) {
Collection<Student> vals = map.values();
for (Iterator<Student> it = vals.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
Map.Entry 物件,記憶體佔用最小。public static void traverseKeySet(Map<String, Student> map) {
for (String key : map.keySet()) {
Student s = map.get(key); // 額外一次 hash 查找
System.out.println(s);
}
}
map.get(key),兩次 hash 計算 + 可能的桶掃描,大資料量下效能明顯劣於 entrySet。map.remove(key))。public static void traverseEntrySet(Map<String, Student> map) {
for (Map.Entry<String, Student> entry : map.entrySet()) {
System.out.println(entry.getKey() + " ---> " + entry.getValue());
}
}
Map.Entry 為內部類,輕量級,JIT 易優化。| 方式 | 平均耗時 (ms/op) | 相對基準 |
|---|---|---|
| entrySet + for-each | 1.02 | 1.00x (基準) |
| values + for-each | 0.98 | 0.96x (略快,無 Entry) |
| keySet + get | 1.45 | 1.42x (明顯較慢) |
實際差距取決於 Key 複雜度、Hash 衝突率、JIT 編譯狀態。但 keySet+get 額外查找的理論劣勢始終存在。
// forEach + Lambda
map.forEach((k, v) -> System.out.println(k + "=" + v));
// Stream 管線(需轉換、過濾、收集時)
map.entrySet().stream()
.filter(e -> e.getValue().getAge() > 30)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
map.remove(key) 或 map.put(...) 會拋 ConcurrentModificationException。解法:Iterator.remove()、收集待刪 key 稍後處理、或用 ConcurrentHashMap。keySet() 返回的 Set 是即時視圖,遍歷期間若其他執行緒修改 Map,行為未定義(非同步 Map)。entrySet().iterator() 返回的 Iterator 在 next() 時可能重用同一 Entry 實例(視實現類),切勿將 Entry 存入集合供後續使用,應立即提取 key/value。Map 遍歷沒有銀彈,**「同時需 key/value → entrySet;只需值 → values;需 key 篩選/刪除 → keySet」** 是不會出錯的肌肉記憶。理解底層 HashMap 查找機制,就能在程式碼審查時一眼識別 keySet+get 的效能隱患,寫出既清晰又高效的程式碼。