Java Map的演進歷史:從JDK 1.7到JDK 21
Java 中的Map接口是集合框架中一個非常重要的組成部分,它用于存儲鍵值對。從 JDK 1.7 到最新的長期支持版本 JDK 21,Map接口經歷了一系列的演變和發展,引入了許多新的方法和改進了已有功能,以更好地滿足開發者的需要。下面我們將詳細介紹這些變化。
JDK 1.7 中的 Map 接口
在 JDK 1.7 時期,Map接口已經相當成熟,提供了一套基礎但強大的 API 來操作鍵值對數據結構。此時的Map接口主要包含了如下一些常用的方法:
- put(K key, V value): 向Map中添加鍵值對。
- get(Object key): 根據給定的鍵獲取對應的值。
- remove(Object key): 移除指定鍵及其關聯的值。
- containsKey(Object key): 判斷此Map是否包含指定鍵。
- containsValue(Object value): 判斷此Map是否包含指定值。
- size(): 返回Map中的鍵值對數量。
- isEmpty(): 測試Map是否為空。
- keySet()、values()、entrySet() 分別返回所有鍵、所有值以及所有鍵值對的集合視圖。
此外,JDK 1.7 還特別為并發場景下的Map實現(即ConcurrentHashMap)做了一些優化,比如使用分段鎖來提高多線程環境下的性能。
JDK 8 對 Map 得增強
到了 JDK 8,Map接口迎來了幾個顯著的變化。
(1) 新增方法
// 1. 獲取指定鍵對應的值,如果鍵不存在,則返回一個默認值。
V getOrDefault(Object key, V defaultValue)
// 2. 使用函數接口遍歷 Map中得鍵值對。
void forEach(BiConsumer<? super K, ? super V> action)
// 3. 使用函數接口替換 Map中得鍵值對。
void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
// 4. 只有在給定的鍵尚未與值關聯時(或者關聯值未 null),才會將指定的鍵值對插入到此Map中。
V putIfAbsent(K key, V value)
// 5. 用于根據給定的鍵(key)以及一個能根據鍵和當前值生成新值的函數(rf),
// 來對 Map 中的鍵值對進行動態計算和更新操作。
V compute(K key, BiFunction<? super K, ? super V, ? extends V> rf)
// 6. 只有在給定的鍵尚未與值關聯時(或者關聯值未 null),才會將指定的鍵值對插入到此Map中,
// 注意如果 mf 函數返回 null 則不會插入。
V computeIfAbsent(K key, Function<? super K, ? extends V> mf)
// 7. 用于在 Map 中處理鍵值對的合并操作,根據指定的鍵(key)、一個默認值(value)
// 以及一個用于合并計算的函數(rf)來決定如何更新或添加鍵值對。
V merge(K key, V value, BiFunction<? super K, ? super V, ? extends V> rf)
// 8. 下方兩個方法是 Map.Entry中新增得兩個靜態方法,分別用于對Map的Entry集合進行排序操作,
// 排序得依據分別是 key 得自然序或者 value 得自然序。當然這兩個方法也支持傳入自定義排序函數。
static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey()
static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)
// 9. 僅當指定的鍵當前映射到指定的值時,才刪除該鍵值對。
boolean remove(Object key, Object value)
// 10. 將指定鍵(key)在Map中所關聯的值替換為新給定的值(value)。
V replace(K key, V value)
// 11. 用于在Map中替換與指定鍵(key)相關聯的值。但替換操作是有條件的,
// 只有當傳入 key 得映射值 與傳入的要被替換的舊值 oldValue 參數完全匹配時,
// 才會將該鍵對應的的值替換為新的值 newValue。
V replace(K key, V oldValue, V newValue)
// 12. 對 Map 中的每一個鍵值對,根據給定的一個二元函數(function)來更新鍵值對的值部分。
V replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
(2) 并發優化
對于 Map 接口得并發實現類ConcurrentHashMap,JDK 1.8 對 ConcurrentHashMap 進行了重大修改,采用了 CAS(Compare - And - Swap)操作和 Synchronized 關鍵字來實現更細粒度的并發控制,摒棄了原來的分段鎖機制并且修改了歷程數據結構,可以實現更高效得讀寫操作。性能方面,當數據量較大且hash沖突比較嚴重時,查詢效率也得到了顯著提升。
JDK 9 至 JDK 21 中 Map 的發展
從 JDK 9 開始直到最新的 JDK 21,雖然沒有像 JDK 8 那樣大規模地擴展Map接口的功能,但是仍然有一些細微但實用的內部優化被引入進來。
(1) JDK 9 更新
JDK 9 為 Map 接口提供了一系列的靜態工廠方法,如 of() 方法。這些方法可以更方便地創建不可變的 Map 實例
// 不可變Map創建
static <K,V> Map<K,V> of()
static <K,V> Map<K,V> of(K k1, V v1)
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2)
// ... 最多支持10個鍵值對
// 超過10個鍵值對使用
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
這種方式創建的Map是不可變的,任何嘗試修改它的操作都會拋出 UnsupportedOperationException。它在需要創建固定內容的Map時非常有用,比如配置信息、常量Map等場景
(2) JDK 10 - JDK 21 的改動和新增方法(相對穩定階段)
在這期間Map接口本身沒有太多大規模的改動,但 Java 整體的性能優化和內部實現的調整可能會間接地影響Map的性能。例如 JDK 在垃圾回收機制、內存管理等方面的改進都會對存儲在中的對象的生命周期和內存占用產生積極的影響。
JDK 10 新增了一個 copyOf() 方法,用于創建一個現有Map的不可變副本。
// 創建不可變副本
static <K,V> Map<K,V> copyOf(Map<? extends K,? extends V> map)
其他三方庫
雖然 Map 接口在 JDK 10 之后再無大的變動,但是還有很多其他三方庫實現了對 Map 得增強。
1.Google 的 Guava 庫
(1) 創建方式的增強
多種類型 Map 創建和靈活的不可變 Map 創建,Maps.newHashMap()來創建類似java.util.HashMap的普通可變Map,如Maps.immutableMap()用來創建不可變Map。
(2) 強大的轉換操作(鍵值對轉換)、過濾操作(按鍵或值過濾)
Maps.transformEntries():該方法允許根據一個自定義的函數來轉換Map中的每個鍵值對、Maps.filterKeys()和Maps.filterValues():這兩個方法分別用于對Map的鍵和值進行過濾。
(3) 引入了能處理一對多關系的 Multimap(多值)
Guava 的Multimap是一種特殊的Map,它允許一個鍵對應多個值,解決了傳統Map在處理一對多關系時的不便。這在很多實際場景中非常有用,比如一個學生可以選多門課程,一本書可以有多個作者等。
(4) 比較兩個 Map 之間得差異
Maps.difference()方法用于比較兩個Map之間的差異。它返回一個MapDifference對象,這個對象包含了豐富的信息,如只在左邊Map出現的鍵值對、只在右邊Map出現的鍵值對、在兩個Map中都出現但值不同的鍵值對以及在兩個Map中都出現且值相同的鍵值對等。這對于數據同步、版本比較等場景非常有幫助。
2.Apache 的 Commons Collections庫
(1) 雙向映射(BidiMap)
Commons Collections 中的 BidiMap 是一種特殊的 Map,它提供了雙向查找的功能。傳統的 Map 是通過鍵查找值,而 BidiMap 允許通過值反向查找鍵。這種雙向映射在很多場景下非常有用,例如在用戶認證系統中,可能需要通過用戶 ID(鍵)查找用戶詳細信息(值),同時也需要通過用戶的某些唯一標識(如用戶名)來獲取用戶 ID。
(2) 多值映射(MultiValueMap)
MultiValueMap 用于處理一個鍵對應多個值的情況,類似于 Guava 中的 Multimap。它提供了方便的方法來添加、獲取和操作多值映射。這種數據結構在處理具有一對多關系的數據時非常有用,比如在權限管理系統中,一個用戶角色(鍵)可能對應多個權限(值)。
(3) 基于 Transformer 和 Closure 的操作
- Transformer(轉換):可以用于將Map中的元素從一種形式轉換為另一種形式。它接受一個輸入對象,并返回轉換后的對象。通過Transformer,可以對Map中的值進行各種數學運算、格式化等操作。例如,將Map中的所有整數值乘以一個固定的系數。
- Closure(閉包):用于對Map中的元素執行某種操作,如更新值等。它可以在不返回新對象的情況下直接修改Map中的元素,這在需要批量修改Map元素的場景下非常有用,比如將Map中所有字符串值轉換為大寫形式。
3.Eclipse 的Collections庫
其實 Eclipse 基金會也創建一個開源的 Java 集合框架庫叫 eclipse-collections。它具有優化的數據結構和豐富、實用且流暢的 API。
只是目前使用使用的人比較少,這里給大家簡單介紹一下功能,有興趣可以自行去 github 閱讀官方說明。
github 地址:https://github.com/eclipse/eclipse-collections
- 對 Map 的主要增強如下:
- 創建和初始化可變與不可變 Map。
- 操作鏈支持增強: MutableMap 支持操作鏈,它可以在一個表達式中連續進行多個操作,如添加、刪除和更新鍵值對。例如MutableMap<String, Integer> mutableMap = Maps.mutable.of("key1", 1, "key2", 2).put("key3", 3);
- 分組功能增強: 提供了強大的 GroupBy 操作,可以根據指定的規則對 Map 中的元素進行分組
- 視圖和迭代增強: 可以創建只讀視圖。
- 與其他集合類型協同操作增強: 對于 Map 來說,它可以方便裝換成其他集合(如 List、Set 等)。
總結
本文給大家介紹了關于 Java 中 Map 接口在 JDK1.7 到 JDk21 中演進以及新增方法,還介紹了一些 Guava、Commons Collections、Eclipse Collections 對 Map 的增強,希望大家喜歡。