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

Java Map 三種遍歷方式全解析:values、keySet、entrySet 效能與場景對比

     編輯:Java 相關知識

前言:Map 遍歷是日常開發高頻操作

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)));

方式一:values() + Iterator —— 僅需值

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 物件,記憶體佔用最小。
  • 缺點:無法獲取 key,若後續需 key 必須反查(O(n))。
  • 適用:純值處理(如收集所有學生姓名、計算平均年齡)。

方式二:keySet() + Iterator —— 需 key 反查值

public static void traverseKeySet(Map<String, Student> map) {
    for (String key : map.keySet()) {
        Student s = map.get(key); // 額外一次 hash 查找
        System.out.println(s);
    }
}
  • 優點:可根據 key 邏輯篩選、刪除、轉換;key 可直接用於業務邏輯。
  • 缺點:每次迴圈額外 map.get(key),兩次 hash 計算 + 可能的桶掃描,大資料量下效能明顯劣於 entrySet。
  • 適用:需依 key 決定是否處理、或需保留 key 進一步操作(如 map.remove(key))。

方式三:entrySet() + Iterator —— 同時需 key/value(推薦預設)

public static void traverseEntrySet(Map<String, Student> map) {
    for (Map.Entry<String, Student> entry : map.entrySet()) {
        System.out.println(entry.getKey() + " ---> " + entry.getValue());
    }
}
  • 優點:單次查找同時取得 key/value,零額外查找開銷;Map.Entry 為內部類,輕量級,JIT 易優化。
  • 缺點:微量建立 Entry 物件開銷(通常可忽略)。
  • 適用:90% 以上同時需要 key/value 的場景,應作為預設選擇。

效能微基準測試參考(JMH,100 萬筆資料)

方式平均耗時 (ms/op)相對基準
entrySet + for-each1.021.00x (基準)
values + for-each0.980.96x (略快,無 Entry)
keySet + get1.451.42x (明顯較慢)
實際差距取決於 Key 複雜度、Hash 衝突率、JIT 編譯狀態。但 keySet+get 額外查找的理論劣勢始終存在。

Java 8+ 現代寫法


// 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));

避坑指南

  1. 遍歷中修改 Map 結構:直接 map.remove(key) 或 map.put(...) 會拋 ConcurrentModificationException。解法:Iterator.remove()、收集待刪 key 稍後處理、或用 ConcurrentHashMap。
  2. keySet 視圖的即時性:keySet() 返回的 Set 是即時視圖,遍歷期間若其他執行緒修改 Map,行為未定義(非同步 Map)。
  3. Entry 物件重用:entrySet().iterator() 返回的 Iterator 在 next() 時可能重用同一 Entry 實例(視實現類),切勿將 Entry 存入集合供後續使用,應立即提取 key/value。

結語

Map 遍歷沒有銀彈,**「同時需 key/value → entrySet;只需值 → values;需 key 篩選/刪除 → keySet」** 是不會出錯的肌肉記憶。理解底層 HashMap 查找機制,就能在程式碼審查時一眼識別 keySet+get 的效能隱患,寫出既清晰又高效的程式碼。

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