Java Map 原理、實(shí)現(xiàn)與使用場(chǎng)景分析
Java Map 詳解:原理、實(shí)現(xiàn)與使用場(chǎng)景
一、介紹
Map 是 Java 集合框架(java.util)中鍵值對(duì)(Key-Value) 形式的集合接口,與 List/Set 并列(繼承自 Collection 的父接口 Iterable,但不直接繼承 Collection)。核心特征是:鍵(Key)唯一且無序(部分實(shí)現(xiàn)有序),值(Value)可重復(fù),通過鍵快速查找值,是日常開發(fā)中存儲(chǔ)關(guān)聯(lián)數(shù)據(jù)的核心工具。
特性:
- 鍵值對(duì)映射:每個(gè) Key 對(duì)應(yīng)唯一的 Value,Key 不允許重復(fù)(若重復(fù)插入,新 Value 會(huì)覆蓋舊 Value);
- Key 不可變性:作為 Key 的對(duì)象需重寫
hashCode()和equals()方法(保證哈希一致性),且建議使用不可變類型(如 String、Integer),避免修改后哈希值變化導(dǎo)致無法查找; - 支持 null:不同實(shí)現(xiàn)類對(duì) null 的支持不同(如 HashMap 允許 1 個(gè) null Key + 多個(gè) null Value,Hashtable 不允許 null);
- 無序性(默認(rèn)):大部分 Map 實(shí)現(xiàn)(如 HashMap)不保證鍵值對(duì)的存儲(chǔ) / 遍歷順序,有序?qū)崿F(xiàn)需顯式指定(如 TreeMap、LinkedHashMap)。
二、Map 接口核心方法
Map 定義了鍵值對(duì)操作的核心方法,覆蓋增刪改查、遍歷等場(chǎng)景:
| 方法 | 作用 |
|---|---|
V put(K key, V value) | 添加 / 替換鍵值對(duì):Key 不存在則新增,存在則替換 Value,返回舊 Value(無則返回 null) |
V get(Object key) | 根據(jù) Key 獲取 Value,Key 不存在返回 null |
V remove(Object key) | 刪除指定 Key 的鍵值對(duì),返回被刪除的 Value(無則返回 null) |
boolean containsKey(Object key) | 判斷是否包含指定 Key |
boolean containsValue(Object value) | 判斷是否包含指定 Value(需遍歷,效率低) |
int size() | 返回鍵值對(duì)數(shù)量 |
boolean isEmpty() | 判斷是否為空 |
void clear() | 清空所有鍵值對(duì) |
Set<K> keySet() | 返回所有 Key 的 Set 集合(視圖,修改會(huì)同步到原 Map) |
Collection<V> values() | 返回所有 Value 的 Collection 集合(視圖) |
Set<Map.Entry<K,V>> entrySet() | 返回鍵值對(duì)(Entry)的 Set 集合(遍歷最優(yōu)方式) |
三、Map 主要實(shí)現(xiàn)類
Java 提供了多款 Map 實(shí)現(xiàn)類,適配不同的性能、有序性、并發(fā)場(chǎng)景,核心如下:
1. HashMap(最常用)
底層實(shí)現(xiàn):JDK 8 前 = 數(shù)組(哈希桶)+ 鏈表;JDK 8 后 = 數(shù)組 + 鏈表 / 紅黑樹(鏈表長(zhǎng)度 ≥8 轉(zhuǎn)紅黑樹,≤6 轉(zhuǎn)回鏈表)。
補(bǔ)充:
HashMap 的底層由「哈希桶數(shù)組」+「鏈表 / 紅黑樹」組成,核心設(shè)計(jì)目標(biāo)是:通過哈希算法將 Key 映射到數(shù)組下標(biāo),實(shí)現(xiàn) O (1) 級(jí)別的快速存??;通過鏈表 / 紅黑樹解決「哈希沖突」(不同 Key 哈希值相同的情況)。
- 哈希桶數(shù)組(
table):默認(rèn)初始長(zhǎng)度 16(2 的冪),每個(gè)下標(biāo)對(duì)應(yīng)一個(gè)「桶」,桶內(nèi)存儲(chǔ)鏈表或紅黑樹; - 鏈表:當(dāng)多個(gè) Key 哈希到同一桶時(shí),先以鏈表形式存儲(chǔ);
- 紅黑樹:當(dāng)鏈表長(zhǎng)度 ≥8 且數(shù)組長(zhǎng)度 ≥64 時(shí),鏈表轉(zhuǎn)為紅黑樹(查詢性能從 O (n) 優(yōu)化為 O (log n));當(dāng)紅黑樹節(jié)點(diǎn)數(shù) ≤6 時(shí),轉(zhuǎn)回鏈表(減少紅黑樹維護(hù)開銷)。
紅黑樹:
紅黑樹是帶紅 / 黑顏色標(biāo)記的自平衡二叉查找樹,也是二叉樹的一種,不過有5條規(guī)則對(duì)樹的高度進(jìn)行限制,增刪改查的時(shí)間復(fù)雜度穩(wěn)定在O(logn)
補(bǔ)充一下那5條規(guī)則:
(1)每個(gè)節(jié)點(diǎn)只能為黑色或者紅色
(2)根節(jié)點(diǎn)必須為黑色
(3)所有“空葉子節(jié)點(diǎn)”都是黑色
(4)紅色節(jié)點(diǎn)的父節(jié)點(diǎn)和子節(jié)點(diǎn)都必須為黑色(你也可以這樣理解,不能連續(xù)倆紅色節(jié)點(diǎn))
(5)從任意節(jié)點(diǎn)到它自己所有葉子節(jié)點(diǎn)(在紅黑樹中這個(gè)葉子節(jié)點(diǎn)就是指的空葉子節(jié)點(diǎn))的路徑中,黑色節(jié)點(diǎn)的數(shù)量完全相同
規(guī)則是為了做什么?
保證樹的結(jié)構(gòu)不會(huì)退化成鏈表,同時(shí)保證增刪查改始終是 O (log n)
(補(bǔ):最長(zhǎng)路徑的長(zhǎng)度 ≤ 2 × 最短路徑的長(zhǎng)度,兩者的差值最大為 最短路徑的長(zhǎng)度)

什么是桶?
桶就是 HashMap 哈希桶數(shù)組里的一個(gè)存儲(chǔ)單元,負(fù)責(zé)接收通過哈希計(jì)算分配過來的鍵值對(duì)節(jié)點(diǎn),解決哈希沖突的鏈表 / 紅黑樹也都是在桶內(nèi)部形成的。
簡(jiǎn)單舉個(gè)例子:
把 HashMap 想象成一個(gè) 帶編號(hào)的快遞柜,這個(gè)快遞柜的每個(gè)獨(dú)立格子就是一個(gè) 桶。
- 快遞柜 = 哈希桶數(shù)組(table)快遞柜有很多個(gè)格子,每個(gè)格子都有自己的編號(hào)(比如 0、1、2…15),對(duì)應(yīng) HashMap 數(shù)組的索引。
- 桶 = 快遞柜的單個(gè)格子每個(gè)格子(桶)的作用是存放 “屬于自己” 的快遞。在 HashMap 中,通過哈希計(jì)算,會(huì)把鍵值對(duì)節(jié)點(diǎn)分配到對(duì)應(yīng)編號(hào)的桶里。
- 哈希沖突 = 一個(gè)格子里放多個(gè)快遞
- 理想情況:一個(gè)快遞(節(jié)點(diǎn))對(duì)應(yīng)一個(gè)格子(桶),取件時(shí)直接按編號(hào)找,速度飛快(對(duì)應(yīng) HashMap 的 O (1) 存?。?。
- 現(xiàn)實(shí)情況:可能出現(xiàn)多個(gè)快遞被分配到同一個(gè)格子(不同 Key 計(jì)算出相同的數(shù)組索引),這就是哈希沖突。這時(shí),HashMap 會(huì)在這個(gè)桶里用鏈表把這些節(jié)點(diǎn)串起來;如果鏈表太長(zhǎng)(≥8),就會(huì)變成紅黑樹,提升查詢效率。
核心特點(diǎn):
- 存取效率高(平均 O (1) 時(shí)間復(fù)雜度),基于哈希表快速定位;
- 無序(存儲(chǔ)順序 ≠ 插入順序);
- 允許 1 個(gè) null Key、多個(gè) null Value;
- 非線程安全(多線程操作可能導(dǎo)致死循環(huán)、數(shù)據(jù)丟失);
- 初始容量 16,負(fù)載因子 0.75(擴(kuò)容閾值 = 容量 × 負(fù)載因子,默認(rèn) 12),擴(kuò)容時(shí)容量翻倍,且重新哈希。
機(jī)制詳解:
哈希計(jì)算與索引定位:
HashMap 高效存取的核心是「將 Key 映射到數(shù)組下標(biāo)」,分為兩步:
1.Key 的哈希值計(jì)算
為了減少哈希沖突,HashMap 對(duì) Key 的 hashCode() 做了二次哈希(擾動(dòng)函數(shù)):
static final int hash(Object key) {
int h;
// 核心邏輯:key.hashCode() ^ (h >>> 16)
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 作用:將 Key 的哈希值高 16 位與低 16 位異或,混合高低位特征,減少哈希沖突(尤其數(shù)組長(zhǎng)度較小時(shí),僅低幾位參與取模,混合后能利用高位信息);
- null Key 處理:hash 值固定為 0,因此 null Key 始終映射到數(shù)組索引 0。
2.數(shù)組索引計(jì)算
通過哈希值計(jì)算數(shù)組下標(biāo),利用「位運(yùn)算替代取?!固嵘阅埽▋H當(dāng)數(shù)組長(zhǎng)度為 2 的冪時(shí)生效):
// i = 哈希值 & (數(shù)組長(zhǎng)度 - 1) int index = hash & (table.length - 1);
- 示例:數(shù)組長(zhǎng)度 16(二進(jìn)制 10000),
table.length - 1 = 15(二進(jìn)制 01111);哈希值與 15 按位與,等價(jià)于hash % 16,但位運(yùn)算更快。 - 為什么數(shù)組長(zhǎng)度是 2 的冪?
- → 保證hash & (length-1)等價(jià)于取模,且位運(yùn)算效率遠(yuǎn)高于取模;
- → 擴(kuò)容時(shí),節(jié)點(diǎn)新索引要么不變,要么 = 原索引 + 原數(shù)組長(zhǎng)度(簡(jiǎn)化擴(kuò)容遷移邏輯)。
擴(kuò)容機(jī)制:
條件:
- 首次
put時(shí),table為 null,初始化數(shù)組(默認(rèn)長(zhǎng)度 16,threshold=16×0.75=12); - 后續(xù)
put時(shí),若size > threshold,觸發(fā)擴(kuò)容; - 鏈表長(zhǎng)度≥8 但數(shù)組長(zhǎng)度 < 64 時(shí),優(yōu)先擴(kuò)容(而非樹化)。
步驟:
// 1. 計(jì)算新容量:原容量翻倍(始終保持2的冪)
int newCap = oldCap << 1; // 如16→32,32→64...
// 2. 計(jì)算新擴(kuò)容閾值:newThr = newCap × loadFactor
int newThr = oldThr << 1;
// 3. 創(chuàng)建新數(shù)組(長(zhǎng)度newCap)
Node<K,V>[] newTab = (Node<K,V>[]) new Node[newCap];
table = newTab;
// 4. 遷移原數(shù)組節(jié)點(diǎn)到新數(shù)組
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = table[j]) != null) {
table[j] = null; // 釋放原數(shù)組引用(幫助GC)
// 情況1:桶內(nèi)僅一個(gè)節(jié)點(diǎn),直接計(jì)算新索引并放入
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 情況2:桶內(nèi)是紅黑樹,拆分紅黑樹并遷移
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 情況3:桶內(nèi)是鏈表,拆分鏈表(優(yōu)化:無需重新哈希,僅判斷高位)
else {
// 鏈表拆分規(guī)則:新索引 = 原索引 或 原索引 + oldCap
Node<K,V> loHead = null, loTail = null; // 原索引節(jié)點(diǎn)
Node<K,V> hiHead = null, hiTail = null; // 原索引+oldCap節(jié)點(diǎn)
Node<K,V> next;
do {
next = e.next;
// 核心判斷:哈希值的第n位(oldCap的二進(jìn)制位)是否為0
if ((e.hash & oldCap) == 0) {
// 第n位為0 → 新索引=原索引
if (loTail == null) loHead = e;
else loTail.next = e;
loTail = e;
} else {
// 第n位為1 → 新索引=原索引+oldCap
if (hiTail == null) hiHead = e;
else hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 放入新數(shù)組
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}使用示例:
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Java", 1);
hashMap.put("Python", 2);
Integer value = hashMap.get("Java"); // 獲取值:1
hashMap.put("Java", 3); // 替換值,舊值 1 被覆蓋
各操作邏輯:
1.存
先定位桶 → 無沖突則新增節(jié)點(diǎn) → 有沖突則按鏈表 / 紅黑樹處理 → 替換重復(fù) Key 的 Value → 檢查擴(kuò)容。
2.取
定位桶 → 桶頭匹配直接返回 → 紅黑樹 / 鏈表遍歷匹配 → 未找到返回 null。
3.刪
定位待刪除節(jié)點(diǎn) → 按鏈表 / 紅黑樹規(guī)則刪除 → 更新 size 和 modCount。
2. LinkedHashMap
底層實(shí)現(xiàn):HashMap + 雙向鏈表(維護(hù)插入 / 訪問順序)。
補(bǔ)充:
LinkedHashMap 完全復(fù)用 HashMap 的哈希桶數(shù)組、節(jié)點(diǎn)哈希 / 擴(kuò)容 / 樹化等邏輯,僅通過「雙向鏈表」改造節(jié)點(diǎn)結(jié)構(gòu)、新增頭尾指針,實(shí)現(xiàn)有序性。
- 哈希桶層:和 HashMap 完全一致,負(fù)責(zé)通過哈??焖俣ㄎ还?jié)點(diǎn),解決哈希沖突(鏈表 / 紅黑樹);
- 雙向鏈表層:所有節(jié)點(diǎn)通過
before/after指針串聯(lián),head指向最早節(jié)點(diǎn),tail指向最新節(jié)點(diǎn),專門維護(hù)遍歷順序; - 節(jié)點(diǎn)復(fù)用:LinkedHashMap 的 Entry 繼承 HashMap.Node,因此哈希桶里的節(jié)點(diǎn)同時(shí)屬于「哈希鏈表 / 紅黑樹」和「雙向鏈表」,一個(gè)節(jié)點(diǎn)兩個(gè) “身份”。
什么是節(jié)點(diǎn)?
在 LinkedHashMap(以及 HashMap)中,節(jié)點(diǎn)(Node/Entry) 是存儲(chǔ)「鍵值對(duì)(Key-Value)」的最小單元 —— 就像快遞包裹本身,而桶是放包裹的格子、雙向鏈表是串包裹的繩子。
舉個(gè)具體的例子:
比如你往 LinkedHashMap 里存 put("手機(jī)", 1999)、put("耳機(jī)", 299)、put("充電器", 99):
- 每個(gè)鍵值對(duì)對(duì)應(yīng)一個(gè)「節(jié)點(diǎn)」:
- 節(jié)點(diǎn) 1:Key = 手機(jī),Value=1999;
- 節(jié)點(diǎn) 2:Key = 耳機(jī),Value=299;
- 節(jié)點(diǎn) 3:Key = 充電器,Value=99。
- 這些節(jié)點(diǎn)會(huì)按哈希規(guī)則被分配到不同的桶(比如手機(jī)→3 號(hào)格、耳機(jī)→5 號(hào)格、充電器→3 號(hào)格):
- 3 號(hào)桶里有兩個(gè)節(jié)點(diǎn)(手機(jī) + 充電器),用鏈表串起來;5 號(hào)桶只有耳機(jī)節(jié)點(diǎn)。
- 同時(shí),這三個(gè)節(jié)點(diǎn)會(huì)被雙向鏈表串成
head→手機(jī)→耳機(jī)→充電器→tail(插入順序);如果調(diào)用get("手機(jī)"),節(jié)點(diǎn) 1 會(huì)被挪到尾部,鏈表變成head→耳機(jī)→充電器→手機(jī)→tail(訪問順序)。
核心特點(diǎn):
- 繼承 HashMap 的所有特性,額外保證「有序性」;
- 有序模式:
- 插入順序(默認(rèn)):遍歷順序 = 插入順序;
- 訪問順序:調(diào)用
get()/put()后,該鍵值對(duì)移至鏈表尾部(可實(shí)現(xiàn) LRU 緩存);
- 非線程安全,性能略低于 HashMap(維護(hù)鏈表開銷)。
使用示例(LRU 緩存):
// 初始容量16,負(fù)載因子0.75,訪問順序模式
Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);
lruMap.put("a", 1);
lruMap.put("b", 2);
lruMap.get("a"); // 訪問后,"a" 移至尾部
// 遍歷順序:b → a(訪問順序)
for (Map.Entry<String, Integer> entry : lruMap.entrySet()) {
System.out.println(entry.getKey());
}
3. TreeMap
底層實(shí)現(xiàn):紅黑樹(自平衡的二叉查找樹)。
核心特點(diǎn):
- 有序:默認(rèn)按 Key 的自然順序(如 Integer 升序、String 字典序),或自定義 Comparator 排序;
- 無 null Key(會(huì)拋 NullPointerException),允許 null Value;
- 存取效率 O (log n)(紅黑樹查找 / 插入),低于 HashMap;
- 非線程安全;
- 適合需要排序的場(chǎng)景(如按 Key 范圍查詢)。
使用示例(自定義排序):
// 按 Key 降序排序
Map<Integer, String> treeMap = new TreeMap<>((k1, k2) -> k2 - k1);
treeMap.put(3, "c");
treeMap.put(1, "a");
treeMap.put(2, "b");
// 遍歷順序:3 → 2 → 1
for (Integer key : treeMap.keySet()) {
System.out.println(key);
}
4. Hashtable(古老的線程安全實(shí)現(xiàn))
底層實(shí)現(xiàn):數(shù)組 + 鏈表(JDK 8 未優(yōu)化紅黑樹)。
補(bǔ)充:
Hashtable 無紅黑樹優(yōu)化(全程僅數(shù)組 + 鏈表),線程安全通過「方法級(jí) synchronized」實(shí)現(xiàn),是典型的 “簡(jiǎn)單粗暴” 的線程安全哈希表設(shè)計(jì)。
- 哈希桶數(shù)組:默認(rèn)初始長(zhǎng)度 11(非 2 的冪,區(qū)別于 HashMap),每個(gè)下標(biāo)對(duì)應(yīng)一個(gè)桶,桶內(nèi)存儲(chǔ)鏈表;
- 鏈表:所有哈希沖突的節(jié)點(diǎn)(Key 哈希到同一桶)通過
next指針串聯(lián),無紅黑樹優(yōu)化,鏈表過長(zhǎng)時(shí)查詢性能退化至 O (n); - 線程安全:所有核心方法(put/get/remove)都加
synchronized鎖,鎖粒度為整個(gè) Hashtable 對(duì)象。
核心特點(diǎn):
- 線程安全(方法級(jí)
synchronized鎖),但鎖粒度大,性能差; - 不允許 null Key/Value(拋 NullPointerException);
- 初始容量 11,負(fù)載因子 0.75,擴(kuò)容時(shí)容量 = 原容量 ×2 +1;
- 基本被 ConcurrentHashMap 替代,僅兼容舊代碼時(shí)使用。
機(jī)制詳解:
哈希計(jì)算與擴(kuò)容:
Key 的哈希計(jì)算(無擾動(dòng)優(yōu)化)
Hashtable 的哈希計(jì)算邏輯簡(jiǎn)單,無 HashMap 的 “高低位異或” 擾動(dòng)優(yōu)化,且直接使用 key.hashCode()(不允許 null Key):
// 計(jì)算Key的哈希值(簡(jiǎn)化版) int hash = key.hashCode(); // 計(jì)算數(shù)組索引(取模,而非位運(yùn)算) int index = (hash & 0x7FFFFFFF) % table.length;
擴(kuò)容機(jī)制(區(qū)別于HashMap):
Hashtable 擴(kuò)容觸發(fā)后,新容量 = 原容量 × 2 + 1(而非 HashMap 的翻倍)
// 1. 計(jì)算新容量:原容量 ×2 +1(如 11→23,23→47...)
int newCapacity = table.length * 2 + 1;
// 2. 計(jì)算新擴(kuò)容閾值:newThr = (int)(newCapacity × loadFactor)
int newThreshold = (int)(newCapacity * loadFactor);
// 3. 創(chuàng)建新數(shù)組(長(zhǎng)度newCapacity)
Entry<?,?>[] newTable = new Entry<?,?>[newCapacity];
// 4. 遷移原數(shù)組節(jié)點(diǎn)到新數(shù)組(重新計(jì)算索引,拷貝鏈表)
for (int i = 0; i < table.length; i++) {
Entry<K,V> e = (Entry<K,V>)table[i];
if (e != null) {
table[i] = null; // 釋放原數(shù)組引用
do {
Entry<K,V> next = e.next;
// 重新計(jì)算新索引(取模)
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newTable[index];
newTable[index] = e;
e = next;
} while (e != null);
}
}
// 5. 替換數(shù)組和閾值
table = newTable;
threshold = newThreshold;
差異總結(jié):
容量增長(zhǎng)規(guī)則:2n+1(保證素?cái)?shù),理論減少哈希沖突,但無實(shí)際性能優(yōu)勢(shì)),HashMap 是 2n(2 的冪,位運(yùn)算優(yōu)化);
索引重計(jì)算:每次擴(kuò)容都需對(duì)所有節(jié)點(diǎn)重新取模,無 HashMap 的 “高位判斷” 優(yōu)化,擴(kuò)容開銷更大;
- 觸發(fā)條件:
count ≥ threshold(HashMap 是size > threshold),更早觸發(fā)擴(kuò)容。
5. ConcurrentHashMap(并發(fā)安全)
底層實(shí)現(xiàn):JDK 8 前 = 分段鎖(Segment 數(shù)組 + HashMap);JDK 8 后 = 數(shù)組 + 鏈表 / 紅黑樹 + CAS + synchronized(鎖哈希桶,粒度更細(xì))。
補(bǔ)充:
- 哈希桶層:與 HashMap 一致(數(shù)組長(zhǎng)度為 2 的冪,鏈表≥8 轉(zhuǎn)紅黑樹),但數(shù)組 / 節(jié)點(diǎn)關(guān)鍵字段(
table/val/next)用volatile保證多線程可見性; - 并發(fā)控制層:
- 無競(jìng)爭(zhēng)時(shí):用 CAS 操作新增節(jié)點(diǎn)(無鎖);
- 有競(jìng)爭(zhēng)時(shí):對(duì)「單個(gè)哈希桶」加
synchronized鎖(僅鎖住沖突的桶,其他桶可并行操作); - 擴(kuò)容時(shí):用
ForwardingNode標(biāo)記桶狀態(tài),多線程協(xié)同擴(kuò)容(避免單線程擴(kuò)容瓶頸);
- 節(jié)點(diǎn)特性:
Node節(jié)點(diǎn)的val/next為volatile,保證修改后其他線程能立即感知。
核心特點(diǎn):
- 線程安全(高并發(fā)):JDK 8 后鎖粒度為「單個(gè)哈希桶」,并發(fā)性能遠(yuǎn)高于 Hashtable;
- 支持 1 個(gè) null Key(JDK 8 后)、多個(gè) null Value;
- 存取效率接近 HashMap(并發(fā)場(chǎng)景下);
- 迭代器是 “弱一致性”,不會(huì)拋
ConcurrentModificationException; - 適合高并發(fā)讀寫的場(chǎng)景(如分布式緩存、業(yè)務(wù)核心數(shù)據(jù)存儲(chǔ))。
擴(kuò)容機(jī)制(多線程協(xié)同)
當(dāng) size > 容量 × 負(fù)載因子(默認(rèn)初始容量 16,負(fù)載因子 0.75,閾值 12),且當(dāng)前數(shù)組(table)無其他擴(kuò)容操作時(shí)觸發(fā)。
ConcurrentHashMap 擴(kuò)容由「觸發(fā)線程 + 其他訪問線程」協(xié)同完成,避免單線程擴(kuò)容瓶頸:
- 觸發(fā)條件:
size > sizeCtl(同 HashMap); - 擴(kuò)容流程:
- 創(chuàng)建
nextTable(容量翻倍); - 遍歷
table桶,將桶節(jié)點(diǎn)遷移到nextTable; - 遷移時(shí),將原桶頭節(jié)點(diǎn)替換為
ForwardingNode(標(biāo)記擴(kuò)容中); - 其他線程訪問該桶時(shí),檢測(cè)到
ForwardingNode,會(huì)暫停當(dāng)前操作,協(xié)助擴(kuò)容;
- 創(chuàng)建
- 核心優(yōu)勢(shì):多線程并行遷移不同桶,大幅提升擴(kuò)容效率。
使用示例:
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
// 多線程安全寫入
new Thread(() -> concurrentMap.put("thread1", "data1")).start();
new Thread(() -> concurrentMap.put("thread2", "data2")).start();
// 安全讀取
System.out.println(concurrentMap.get("thread1"));
6. Properties(配置文件專用)
底層實(shí)現(xiàn):繼承 Hashtable,Key/Value 均為 String 類型。
補(bǔ)充:
Properties 完全復(fù)用 Hashtable 的哈希表核心邏輯(數(shù)組 + 鏈表、方法級(jí) synchronized 鎖、禁止 null 值),僅在其基礎(chǔ)上增加「Key/Value 強(qiáng)制為 String 類型」和「配置文件讀寫」的擴(kuò)展方法。
核心存儲(chǔ)層:完全復(fù)用 Hashtable 的 table 數(shù)組(初始容量 11,擴(kuò)容規(guī)則 2n+1)、Entry 鏈表節(jié)點(diǎn),線程安全通過方法級(jí) synchronized 實(shí)現(xiàn);
擴(kuò)展層:
- 類型約束:Key/Value 實(shí)際使用時(shí)強(qiáng)制為 String(雖底層仍存儲(chǔ) Object,但
setProperty()/getProperty()限定為 String); - 配置繼承:通過
defaults實(shí)現(xiàn)配置復(fù)用(當(dāng)前配置無 Key 時(shí),從默認(rèn)配置集查找); - IO 能力:
load()/store()方法解析 / 生成.properties格式的配置文件(鍵值對(duì)以key=value換行存儲(chǔ))。
核心特點(diǎn):
- 專為讀取配置文件(.properties)設(shè)計(jì),支持
load()/store()方法讀寫文件; - 線程安全(繼承 Hashtable 的 synchronized 方法);
- 常用場(chǎng)景:讀取系統(tǒng)配置、項(xiàng)目配置文件。
操作原理:
配置文件加載(load ())
load() 是 Properties 最核心的擴(kuò)展方法,用于從 .properties 文件 / 輸入流解析鍵值對(duì):
// 示例:加載配置文件
Properties props = new Properties();
props.load(new FileInputStream("config.properties"));
解析:
- 按行讀取輸入流,忽略注釋行(以
#/!開頭)和空行; - 解析每行的
key=value格式(支持:/ 空格 作為分隔符),去除首尾空格; - 調(diào)用
put(key, value)(繼承 Hashtable)將鍵值對(duì)存入哈希表; - 全程加
synchronized鎖,保證線程安全。
配置獲取(getProperty ())
getProperty() 是 String 專屬的獲取方法,支持默認(rèn)配置集查找:
String url = props.getProperty("db.url");
// 帶默認(rèn)值:若Key不存在,返回默認(rèn)值"jdbc:mysql://localhost"
String url = props.getProperty("db.url", "jdbc:mysql://localhost");
查找邏輯:
- 調(diào)用 Hashtable 的
get(key)獲取值,強(qiáng)轉(zhuǎn)為 String; - 若值為 null 且
defaults不為 null,遞歸從defaults中查找; - 若仍為 null,返回指定的默認(rèn)值(或 null)。
配置存儲(chǔ)(store ())
store() 用于將配置寫入文件 / 輸出流,生成標(biāo)準(zhǔn) .properties 文件:
props.store(new FileOutputStream("config.properties"), "DB Config");
寫入邏輯:
- 先寫入注釋(可選)和當(dāng)前時(shí)間戳;
- 遍歷哈希表的 Entry 鏈表,按
key=value格式逐行寫入; - 全程加
synchronized鎖,保證寫入過程不被打斷。
基礎(chǔ)操作(put/remove)
Properties 未重寫 Hashtable 的 put()/remove() 方法,完全復(fù)用其邏輯:
setProperty(key, value)本質(zhì)是調(diào)用put(key, value),僅限定參數(shù)為 String;- 所有操作均加
synchronized鎖,線程安全但并發(fā)性能差(同 Hashtable)。
使用示例:
Properties props = new Properties();
// 讀取配置文件
props.load(new FileInputStream("config.properties"));
String url = props.getProperty("db.url"); // 獲取配置值
四、Map 常見使用場(chǎng)景
| 場(chǎng)景 | 推薦實(shí)現(xiàn)類 |
|---|---|
| 日常開發(fā)、無排序、高讀寫性能 | HashMap |
| 需要有序(插入 / 訪問順序)、LRU 緩存 | LinkedHashMap |
| 需要按 Key 排序、范圍查詢 | TreeMap |
| 高并發(fā)讀寫、線程安全 | ConcurrentHashMap |
| 配置文件讀寫 | Properties |
| 舊代碼兼容、低并發(fā)線程安全 | Hashtable |
五、Map 遍歷方式(性能對(duì)比)
Map 遍歷的核心是操作 entrySet()/keySet()/values(),推薦優(yōu)先級(jí):
entrySet 遍歷(最優(yōu)):直接獲取鍵值對(duì),無需二次查詢,性能最高;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
}
迭代器遍歷(支持刪除):適合遍歷中刪除元素,避免 ConcurrentModificationException;
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getValue() == 2) {
it.remove(); // 安全刪除
}
}
keySet + get () 遍歷(低效):需二次哈希查詢,性能最差,不推薦;
for (String key : map.keySet()) {
Integer value = map.get(key); // 額外哈希查詢
}
Lambda 遍歷(簡(jiǎn)潔):JDK 8+ 支持,代碼簡(jiǎn)潔,性能接近 entrySet;
map.forEach((key, value) -> System.out.println(key + ":" + value));
六、注意事項(xiàng)
- Key 的哈希與相等性:
- 自定義對(duì)象作為 Key 時(shí),必須重寫
hashCode()和equals()(保證相同對(duì)象哈希值相同,不同對(duì)象哈希值盡量不同); - 避免使用可變對(duì)象作為 Key(如 ArrayList),修改后哈希值變化會(huì)導(dǎo)致無法查找。
- 自定義對(duì)象作為 Key 時(shí),必須重寫
- HashMap 擴(kuò)容優(yōu)化:
- 已知元素?cái)?shù)量時(shí),提前指定初始容量(如
new HashMap<>(100)),避免頻繁擴(kuò)容(擴(kuò)容需重新哈希,開銷大); - 負(fù)載因子默認(rèn) 0.75 是性能與內(nèi)存的平衡,無需輕易修改。
- 已知元素?cái)?shù)量時(shí),提前指定初始容量(如
- 并發(fā)安全:
- HashMap/LinkedHashMap/TreeMap 非線程安全,多線程修改需手動(dòng)加鎖(如
Collections.synchronizedMap()),或直接使用 ConcurrentHashMap; - ConcurrentHashMap 不支持
null Key(JDK 7)/ 支持 1 個(gè) null Key(JDK 8+),需注意空值處理。
- HashMap/LinkedHashMap/TreeMap 非線程安全,多線程修改需手動(dòng)加鎖(如
- TreeMap 排序:
- 自定義對(duì)象作為 Key 時(shí),需實(shí)現(xiàn)
Comparable接口,或創(chuàng)建 TreeMap 時(shí)指定 Comparator,否則拋ClassCastException。
- 自定義對(duì)象作為 Key 時(shí),需實(shí)現(xiàn)
七、總結(jié)
Map 是 Java 中存儲(chǔ)鍵值對(duì)的核心集合,核心實(shí)現(xiàn)類各有側(cè)重:
- HashMap:性能均衡,適合絕大多數(shù)無排序、非并發(fā)場(chǎng)景;
- LinkedHashMap:有序 + HashMap 特性,適合緩存場(chǎng)景;
- TreeMap:排序 + 范圍查詢,適合有序鍵值對(duì)場(chǎng)景;
- ConcurrentHashMap:高并發(fā)安全,適合核心業(yè)務(wù)的并發(fā)存儲(chǔ);
- Properties:配置文件專用,簡(jiǎn)單易用。
七、總結(jié)
Map 是 Java 中存儲(chǔ)鍵值對(duì)的核心集合,核心實(shí)現(xiàn)類各有側(cè)重:
- HashMap:性能均衡,適合絕大多數(shù)無排序、非并發(fā)場(chǎng)景;
- LinkedHashMap:有序 + HashMap 特性,適合緩存場(chǎng)景;
- TreeMap:排序 + 范圍查詢,適合有序鍵值對(duì)場(chǎng)景;
- ConcurrentHashMap:高并發(fā)安全,適合核心業(yè)務(wù)的并發(fā)存儲(chǔ);
- Properties:配置文件專用,簡(jiǎn)單易用。
選擇時(shí)需根據(jù)「有序性、并發(fā)要求、性能、業(yè)務(wù)場(chǎng)景」四大維度權(quán)衡,同時(shí)注意 Key 的不可變性和遍歷方式的性能優(yōu)化。
附表:
| JDK 版本 | 核心變化點(diǎn) | 涉及 Map 實(shí)現(xiàn)類 | 詳細(xì)說明 |
|---|---|---|---|
| JDK 1.0 | 初始版本發(fā)布 | Hashtable | 1. 引入 Hashtable,底層為「數(shù)組 + 鏈表」,方法級(jí) synchronized 保證線程安全;2. 不支持 null Key/Value,初始容量 11,擴(kuò)容規(guī)則 2n+1;3. 無紅黑樹、無擾動(dòng)哈希、無并發(fā)優(yōu)化。 |
| JDK 1.2 | 集合框架重構(gòu) | HashMap、TreeMap | 1. 引入 HashMap,替代 Hashtable 成為非線程安全哈希表首選;- 底層「數(shù)組 + 鏈表」,初始容量 16(2 的冪),負(fù)載因子 0.75;- 支持 1 個(gè) null Key、多個(gè) null Value;- 實(shí)現(xiàn)擾動(dòng)哈希(高低位異或)、位運(yùn)算索引計(jì)算;2. 引入 TreeMap,底層紅黑樹,支持 Key 自然排序 / 自定義排序,無 null Key;3. 引入 LinkedHashMap(HashMap 子類),雙向鏈表維護(hù)插入 / 訪問順序。 |
| JDK 1.4 | 配置增強(qiáng) | Properties | 1. 基于 Hashtable 擴(kuò)展,強(qiáng)化 .properties 配置文件讀寫能力;2. 新增 loadFromXML()/storeToXML() 方法,支持 XML 格式配置。 |
| JDK 5.0 | 泛型支持 | 所有 Map 實(shí)現(xiàn)類 | 1. 引入泛型(Map<K,V>),替代原始 Object 類型,避免強(qiáng)制類型轉(zhuǎn)換;2. 優(yōu)化 TreeMap 比較器,支持 Comparator<? super K> 泛型約束。 |
| JDK 6.0 | 性能微調(diào) | HashMap、Hashtable | 1. 優(yōu)化 HashMap 哈希算法,減少哈希沖突概率;2. 調(diào)整 Hashtable 擴(kuò)容閾值計(jì)算邏輯,提升低容量場(chǎng)景性能。 |
| JDK 7.0 | 并發(fā)優(yōu)化 | ConcurrentHashMap | 1. 重寫 ConcurrentHashMap,底層「分段鎖(Segment)+ HashMap」;- 分段鎖粒度為 Segment(默認(rèn) 16 個(gè)),并發(fā)度遠(yuǎn)高于 Hashtable;- 不支持 null Key/Value,避免并發(fā)場(chǎng)景空指針歧義;2. HashMap 優(yōu)化擴(kuò)容邏輯,減少擴(kuò)容時(shí)的哈希沖突。 |
| JDK 8.0 | 核心重構(gòu)(里程碑) | HashMap、ConcurrentHashMap、LinkedHashMap | 1. HashMap 重大升級(jí):- 鏈表長(zhǎng)度 ≥8 且數(shù)組長(zhǎng)度 ≥64 時(shí),鏈表轉(zhuǎn)紅黑樹(查詢從 O (n)→O (log n));- 紅黑樹節(jié)點(diǎn)數(shù) ≤6 時(shí),轉(zhuǎn)回鏈表(降低維護(hù)開銷);- 優(yōu)化擴(kuò)容機(jī)制,多線程擴(kuò)容(但仍非線程安全);- 簡(jiǎn)化擾動(dòng)哈希邏輯(key.hashCode() ^ (h >>> 16));2. ConcurrentHashMap 重構(gòu):- 廢棄分段鎖,改用「CAS + synchronized 鎖單個(gè)哈希桶」,并發(fā)粒度更細(xì);- 支持 1 個(gè) null Key(區(qū)別于 JDK 7),兼容 HashMap 用法;- 引入紅黑樹優(yōu)化(同 HashMap);3. LinkedHashMap 優(yōu)化:- 強(qiáng)化 LRU 場(chǎng)景性能,訪問順序模式下節(jié)點(diǎn)遷移效率提升;4. 新增 Lambda 遍歷(forEach() 方法),簡(jiǎn)化遍歷寫法。 |
| JDK 9.0 | 工廠方法增強(qiáng) | 所有 Map 實(shí)現(xiàn)類 | 1. 引入不可變 Map 工廠方法:- Map.of()(最多 10 個(gè)鍵值對(duì))、Map.ofEntries()(不限數(shù)量);- 不可變 Map 不支持增刪改,無 null Key/Value,性能更高。 |
| JDK 11.0 | 性能與安全優(yōu)化 | ConcurrentHashMap、HashMap | 1. 優(yōu)化 ConcurrentHashMap 擴(kuò)容邏輯,多線程協(xié)同擴(kuò)容效率提升;2. 修復(fù) HashMap 極端哈希值下的性能退化問題;3. 增強(qiáng)不可變 Map 的序列化安全性。 |
| JDK 17.0 | 長(zhǎng)期支持版優(yōu)化 | 所有 Map 實(shí)現(xiàn)類 | 1. 穩(wěn)定化不可變 Map 實(shí)現(xiàn),修復(fù)多線程下的弱一致性問題;2. 優(yōu)化 TreeMap 紅黑樹旋轉(zhuǎn)邏輯,減少排序耗時(shí);3. 廢棄 Hashtable 部分過時(shí)方法(如 elements()),推薦 ConcurrentHashMap 替代。 |
到此這篇關(guān)于Java Map 原理、實(shí)現(xiàn)與使用場(chǎng)景分析的文章就介紹到這了,更多相關(guān)Java Map 使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Batch遠(yuǎn)程分區(qū)的本地Jar包模式的代碼詳解
這篇文章主要介紹了Spring Batch遠(yuǎn)程分區(qū)的本地Jar包模式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Spring?Boot條件注解之@ConditionalOnProperty完全解析
這篇文章主要介紹了SpringBoot中的@ConditionalOnProperty注解,通過配置文件屬性值控制Bean或配置類的加載,實(shí)現(xiàn)功能開關(guān)和環(huán)境配置,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
java獲取網(wǎng)絡(luò)圖片上傳到OSS的方法
這篇文章主要為大家詳細(xì)介紹了java獲取網(wǎng)絡(luò)圖片上傳到OSS,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
SpringBoot實(shí)現(xiàn)簡(jiǎn)單文件上傳功能
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)簡(jiǎn)單文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
簡(jiǎn)單談?wù)凾hreadPoolExecutor線程池之submit方法
下面小編就為大家?guī)硪黄?jiǎn)單談?wù)凾hreadPoolExecutor線程池之submit方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
Springboot整合fastdfs實(shí)現(xiàn)分布式文件存儲(chǔ)
本文主要介紹了Springboot整合fastdfs實(shí)現(xiàn)分布式文件存儲(chǔ),詳細(xì)闡述了Springboot應(yīng)用程序如何與FastDFS進(jìn)行集成及演示了如何使用Springboot和FastDFS實(shí)現(xiàn)分布式文件存儲(chǔ),感興趣的可以了解一下2023-08-08

