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

Java Map 遍歷性能大比拼:keySet vs entrySet vs values,HashMap 與 TreeMap 差異深度解析

     編輯:Java 相關知識

引言:為什麼要關心遍歷方式?

Map 遍歷是高頻操作,選錯方式可能導致 10x 性能差距。本文基於 JMH 基準測試,系統對比 keySet、entrySet、values 在 HashMap 與 TreeMap 下的三種場景:遍歷 K+V、僅遍歷 K、僅遍歷 V。

測試環境與數據構造

  • JDK 17, Intel i7-12700H, 16GB RAM
  • 數據量:1,000,000 鍵值對
  • Key 類型:String(數字字符串 "1"..."1000000" vs 稀疏 "50,100,150...")
  • Value 類型:固定字符串 "value"

核心結論速覽表

Map 類型場景最優方式次優最差核心原因
HashMapK+VentrySet ≈ keySet--HashMap get() 極快(O(1)),entrySet 多創建 Entry 對象抵消優勢
僅 KkeySetentrySet-entrySet 多讀 Value 浪費
僅 VvaluesentrySetkeySetkeySet 需額外 get()
TreeMapK+VentrySet ≫ keySet-keySetTreeMap get() 為 O(logN) 紅黑樹查找,entrySet 迭代器直接遍歷節點
僅 KkeySetentrySet-同 HashMap
僅 VvaluesentrySetkeySetkeySet 需 get(),TreeMap 極慢

詳細數據(單位:ms/op,100 萬數據,平均 5 輪)

HashMap - 密集 Key ("1"..."1000000")

方式K+V僅 K僅 V
keySet (for-each)382637
entrySet (for-each)433432
values (for-each)--26

TreeMap - 密集 Key

方式K+V僅 K僅 V
keySet (for-each)43049432
entrySet (for-each)706361
values (for-each)--45

原理深度解析

HashMap:entrySet 為何不穩定佔優?

  • entrySet 迭代器直接遍歷 Node[] table 鏈表/紅黑樹,每次返回 Map.Entry 對象(新建或復用);
  • keySet 迭代器同樣遍歷底層數組,但僅返回 Key;後續 map.get(key) 需重新計算 hash、定位桶、遍歷鏈表比較 equals;
  • 關鍵點:HashMap get() 極快(納秒級),但 entrySet 每次迭代分配/復用 Entry 對象也有開銷。兩者在現代 JVM(逃逸分析、標量替換)下往往持平。

TreeMap:entrySet 完勝的鐵律

  • TreeMap 底層紅黑樹,get(key) 需從根節點向下比較 O(logN),100 萬數據約 20 次比較;
  • keySet 遍歷 + get() = O(N logN);
  • entrySet 迭代器通過 nextEntry() 直接走樹的中序後繼節點,純指針跳轉 O(1) 均攤,總體 O(N);
  • 因此 TreeMap 任何涉及 Value 的遍歷必須用 entrySet/values,keySet 只適合純 Key 遍歷。

最佳實踐代碼模板


// 1. 同時需要 Key 和 Value → entrySet (通用最優,TreeMap 必須)
for (Map.Entry entry : map.entrySet()) {
    K k = entry.getKey();
    V v = entry.getValue();
    // process
}

// 2. 僅需要 Key → keySet
for (K k : map.keySet()) { /* process */ }

// 3. 僅需要 Value → values
for (V v : map.values()) { /* process */ }

// 4. Java 8+ Stream 風格(性能略低於 for-each,但可並行)
map.forEach((k, v) -> { /* process */ });           // K+V
map.keySet().forEach(k -> { /* process */ });       // K
map.values().forEach(v -> { /* process */ });       // V
map.entrySet().parallelStream().forEach(e -> { });  // 並行遍歷大數據

避坑指南

  1. 不要在遍歷中修改 Map:拋 ConcurrentModificationException。需修改請用 ConcurrentHashMap 或收集待刪 Key 後二次處理;
  2. LinkedHashMap 遍歷順序 = 插入順序/訪問順序,entrySet/keySet/values 均保持順序;
  3. EnumMap/IdentityHashMap/WeakHashMap 同理:涉及 Value 用 entrySet/values;
  4. 微優化:極熱路徑可將 entrySet() 結果賦值給局部變量,避免重複調用(JIT 通常會內聯消除)。

總結

HashMap:K+V 用 entrySet 或 keySet 差異微小,僅 K 用 keySet,僅 V 用 values。
TreeMap:凡涉及 Value 必須用 entrySet 或 values,keySet 僅限純 Key 遍歷。
記住口訣:「紅黑樹見值用 entry,哈希表隨意;只取鍵用 keySet,只取值用 values。」

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