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

Java Map 排序完全指南:TreeMap 按鍵排序、按值排序轉換技巧、LinkedHashMap 保持順序與性能對比

     編輯:Java 相關知識

前言:Map 本無序,排序靠轉換

Java Map 接口不保證順序。HashMap 無序、LinkedHashMap 維護插入/訪問順序、TreeMap 紅黑樹天然按鍵有序。本文系統講解三大 Map 特性、TreeMap 按鍵排序定製、通用按值排序轉換模式、性能基準與場景選型指南。

一、三大 Map 核心特性對比

特性HashMapLinkedHashMapTreeMap
底層結構數組 + 鏈表/紅黑樹 (JDK 8+)HashMap + 雙向鏈表紅黑樹
順序保證無序 (隨機)插入順序 / 訪問順序 (accessOrder)按鍵自然序 / Comparator 定製序
鍵值為 null允許 1 個 null key、多個 null value同 HashMap不允許 null key (拋 NPE),允許 null value
線程安全非線程安全非線程安全非線程安全
時間複雜度O(1) 平均O(1) 平均O(log n)
遍歷性能受容量影響僅受元素數影響,穩定有序遍歷,O(n)
適用場景高頻增刪改查、無序需求LRU 緩存、需保持輸入順序範圍查詢、按鍵排序輸出、導航操作

二、TreeMap 按鍵排序:自然序與定製 Comparator

1. 自然順序 (實現 Comparable)

Map map = new TreeMap<>(); // Integer 實現 Comparable
map.put(3, "C"); map.put(1, "A"); map.put(2, "B");
// 遍歷: 1=A, 2=B, 3=C

2. 定製 Comparator (降序、複雜邏輯)

Map descMap = new TreeMap<>((k1, k2) -> k2 - k1); // 降序
// 或
Map customMap = new TreeMap<>((k1, k2) -> {
    int cmp = k1.getPriority() - k2.getPriority();
    return cmp != 0 ? cmp : k1.getId() - k2.getId(); // 次級排序
});

注意:Comparator 必須與 equals 一致 (即 compare(a,b)==0 <=> a.equals(b)),否則違反 SortedMap 契約,導致鍵「丟失」或重複。

3. 導航操作 (NavigableMap)

NavigableMap nm = new TreeMap<>();
nm.lowerKey(5);    // < 5 最大鍵
nm.floorKey(5);    // ≤ 5 最大鍵
nm.ceilingKey(5);  // ≥ 5 最小鍵
nm.higherKey(5);   // > 5 最小鍵
nm.subMap(1, true, 10, false); // [1, 10) 範圍視圖
nm.headMap(5, true); // ≤ 5
nm.tailMap(5, false); // > 5

三、按值排序:通用轉換模式

Map 無直接按值排序能力,核心思路:**EntrySet → List → Sort → LinkedHashMap**

1. 標準實現 (Java 8+ Stream)

public static > Map sortByValue(Map map) {
    return map.entrySet().stream()
        .sorted(Map.Entry.comparingByValue().reversed()) // 降序
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            Map.Entry::getValue,
            (v1, v2) -> v1, // 鍵衝突策略 (理論上不衝突)
            LinkedHashMap::new // 保持插入順序
        ));
}

2. 兼容舊版本 / 更精確控制

public static  Map sortByValue(Map map, Comparator valueComparator) {
    List> entries = new ArrayList<>(map.entrySet());
    entries.sort((e1, e2) -> valueComparator.compare(e1.getValue(), e2.getValue()));
    
    Map result = new LinkedHashMap<>(entries.size() * 4 / 3 + 1); // 預分配避免擴容
    for (Map.Entry entry : entries) {
        result.put(entry.getKey(), entry.getValue());
    }
    return result;
}

// 調用
Map wordCount = ...;
Map sorted = sortByValue(wordCount, Comparator.reverseOrder());

3. 保留原 Map 類型 (如 ConcurrentHashMap)

public static > M sortByValue(M map, Comparator cmp, Supplier mapFactory) {
    return map.entrySet().stream()
        .sorted(Map.Entry.comparingByValue(cmp))
        .collect(Collectors.toMap(
            Map.Entry::getKey, Map.Entry::getValue,
            (v1, v2) -> v1, mapFactory));
}
// 調用
ConcurrentHashMap sorted = sortByValue(chMap, Comparator.naturalOrder(), ConcurrentHashMap::new);

四、性能基準與選型建議

操作HashMapLinkedHashMapTreeMap按值排序 (轉換)
單次 put/get~50 ns~55 ns~120 nsN/A
遍歷 10萬條~3 ms~2 ms~4 ms~15 ms (含排序)
內存開銷基準+ 兩個引用/節點+ 紅黑樹顏色/父指針+ List + 新 Map
線程安全變體ConcurrentHashMap無 (需同步包裝)無 (需同步包裝)同步包裝結果 Map

五、常見坑點與避坑指南

  1. TreeMap 鍵不可為 null:插入 null key 直接拋 NullPointerException。解決:用空字符串/特殊對象代替,或改用 ConcurrentSkipListMap (支援 null key)。
  2. Comparator 與 equals 不一致:導致 put 不覆蓋舊值、get 找不到、size 異常。鐵律:compare(a,b)==0 <=> a.equals(b)。
  3. 按值排序後修改原值:排序結果 Map 中的 Value 仍引用原對象,若原對象可變且被修改,順序邏輯失效。解決:排序時複製值、或用不可變對象。
  4. 大數據量排序 OOM:百萬級 Entry 轉 List 佔用大量堆。解決:流式處理、外部排序、資料庫 ORDER BY 下推。
  5. LinkedHashMap accessOrder 陷阱:get/put 會移動節點,並發環境下極易導致鏈表斷裂/循環。並發場景用 ConcurrentHashMap + 獨立順序維護 (如時間戳)。

六、總結

按鍵排序用 TreeMap (自然序/Comparator/NavigableMap);按值排序轉 LinkedHashMap (Stream/List.sort/自定義 Comparator);保持插入順序用 LinkedHashMap。選型決策樹:

  1. 需範圍查詢/鍵有序遍歷 → TreeMap
  2. 需 LRU/插入順序/訪問順序 → LinkedHashMap
  3. 純高併發無序查找 → ConcurrentHashMap
  4. 需按值排序輸出 → HashMap → List.sort → LinkedHashMap
  5. 需按值排序並併發讀寫 → ConcurrentHashMap → 排序 → ConcurrentSkipListMap/同步 LinkedHashMap

掌握 Map 順序本質與轉換模式,即可在性能、功能、線程安全三角中精準取捨。

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