本文將為大家詳細講解Java中的Map集合,這是我們進行開發時經常用到的知識點,也是大家在學習Java中很重要的一個知識點,更是我們在面試時有可能會問到的問題,
文章較長,干貨滿滿,建議大家收藏慢慢學習,文末有本文重點總結,主頁有全系列文章分享,技術類問題,歡迎大家和我們一起交流討論!
前言
在上一篇文章中給大家講解了Java里的Set集合及其常用子類,現在我們已經掌握了Java里的兩大集合,最后還有另一大集合等待著我們學習,這就是Map集合,與之前的集合不太一樣,Map集合屬于雙列集合,該集合中的資訊是key-value形式;而之前的LIst和Set都是單列集合,里面的元素沒有key,
有些小伙伴可能會很好奇,我們已經學習了List和Set集合了,為什么還要再搞出來一個Map集合呢?Map集合與Collection集合又有什么不同呢? 要想搞清楚以上問題,我們可以考慮這么一個需求,
假如我們利用List集合,來存盤一份Student學生資訊,要求你根據name來查找某個指定的學生分數,你要怎么實作?按照我們之前的經驗,常用的方式就是遍歷這個List,并挨個判斷name是否相等,找到后就回傳指定元素,
其實這種需求非常的常見,通過一個關鍵詞來查詢對應的值,如果我們使用List來實作該需求,效率會非常的低,因為平均需要掃描完集合的一半元素才能確定,而如果使用Map這種key-value鍵值對映射表的資料結構,就可以通過key快速地查找到value值,
全文大約【8500】 字,不說廢話,只講可以讓你學到技術、明白原理的純干貨!本文帶有豐富的案例及配圖視頻,讓你更好地理解和運用文中的技術概念,并可以給你帶來具有足夠啟迪的思考......
一. Map集合
1. 簡介
Map集合是一種以鍵值對形式存盤和操作資料的資料結構,建立了key-value之間的映射關系,常用于存盤和處理復雜的資料,同時Map也是一種雙列集合介面,它有多個實作類,包括HashMap、TreeMap、LinkedHashMap等,最常用的是HashMap類,其中,HashMap是按哈希演算法來實作存取鍵物件的,這是我們開發時最常用的Map子類,而TreeMap類則可以對鍵物件進行排序,
Map集合中的每個元素,都包含了一個鍵(key)和一個值(value),key和value組成了鍵-值的映射表,我們稱其為鍵值對,鍵用于唯一標識一個元素,值用于存盤該元素的資料,一般情況下,這個key和value可以是任何參考型別的資料,其中,鍵key是無序、無下標、不重復的,最多只能有一個key為null,值value是無序、無下標、可重復的,可以有多個value為null,
并且這個key和value之間是單向的一對一關系,即通過指定的key,總能找到唯一的、確定的value,當我們想從Map中取出資料時,只要給出指定的key,就能取出對應的value,
配套視頻如下 :戳我直達視頻
2. 特點
根據上面我們對Map概念的講解,把Map的主要特點給大家總結如下:
- Map 和 List 不同,Map是一種雙列集合;
- Map 存盤的是 key-value 的映射關系;
- Map 不保證順序 ,在遍歷時,遍歷的順序不一定是 put() 時放入的 key 的順序,也不一定是 key 的排序順序,
3. 實作方式
在Java中,Map集合的實作方式主要有兩種:基于哈希表和基于樹結構,接下來給大家簡單介紹一下基于這兩種結構的Map集合,
3.1 基于哈希表的Map集合
基于哈希表的Map,其底層是基于哈希表作為資料結構的集合,主要的實作類是HashMap,
在HashMap中,每個元素都包含一個鍵和一個值,當我們在添加元素時,HashMap會根據鍵的哈希值計算出對應的桶(Bucket),并將元素存盤到該桶中,如果不同的鍵具有相同的哈希值,就會出現哈希沖突,此時HashMap會使用鏈表或紅黑樹等資料結構來解決沖突,基于哈希表的Map集合具有快速的添加、洗掉和查找元素的特點,但元素的順序是不確定的,
3. 實作方式
在Java中,Map集合的實作方式主要有兩種:基于哈希表和基于樹結構,接下來壹哥給大家簡單介紹一下基于這兩種結構的Map集合,
3.1 基于哈希表的Map集合
基于哈希表的Map,其底層是基于哈希表作為資料結構的集合,主要的實作類是HashMap,在HashMap中,每個元素都包含一個鍵和一個值,當我們在添加元素時,HashMap會根據鍵的哈希值計算出對應的桶(Bucket),并將元素存盤到該桶中,如果不同的鍵具有相同的哈希值,就會出現哈希沖突,此時HashMap會使用鏈表或紅黑樹等資料結構來解決沖突,基于哈希表的Map集合具有快速的添加、洗掉和查找元素的特點,但元素的順序是不確定的,
3.2 基于樹結構的Map集合
基于樹結構的Map集合,其底層是基于紅黑樹作為資料結構的集合,主要的實作類是TreeMap,在TreeMap中,每個元素也都包含一個鍵和一個值,我們在添加元素時,TreeMap會根據鍵的比較結果,將元素存盤到正確的位置上,使得元素可以按照鍵的升序或降序排列,基于樹結構的Map集合,具有快速查找和遍歷元素的特點,但添加和洗掉元素的速度較慢,
4. 常用方法
Map集合的使用和其他集合類似,主要包括添加、洗掉、獲取、遍歷元素等操作,
當我們呼叫put(K key, V value)方法時,會把key和value進行映射并放入Map,當呼叫V get(K key)時,可以通過key獲取到對應的value;如果key不存在,則回傳null,如果我們只是想查詢某個key是否存在,可以呼叫containsKey(K key)方法,另外我們也可以通過 Map提供的keySet()方法,獲取所有key組成的集合,進而遍歷Map中所有的key-value對,
下表中就是Map里的一些常用方法,大家可以記一下,這些方法在Map的各實作子類中也都存在,
| 方法 | 描述 |
|---|---|
| clear() | 洗掉Map中所有的鍵-值對 |
| isEmpty() | 判斷Map是否為空 |
| size() | 計算Map中鍵/值對的數量 |
| put() | 將鍵/值對添加到Map中 |
| putAll() | 將所有的鍵/值對添加到Map中 |
| putIfAbsent() | 如果Map中不存在指定的鍵,則將指定的鍵/值對插入到Map中, |
| remove() | 洗掉Map中指定鍵key的映射關系 |
| containsKey() | 檢查Map中是否存在指定key對應的映射關系, |
| containsValue() | 檢查Map中是否存在指定的 value對應的映射關系, |
| replace() | 替換Map中指定key對應的value, |
| replaceAll() | 將Map中所有的映射關系替換成給定函式執行的結果, |
| get() | 獲取指定 key 對應對 value |
| getOrDefault() | 獲取指定 key 對應對 value,如果找不到 key ,則回傳設定的默認值 |
| forEach() | 對Map中的每個映射執行指定的操作, |
| entrySet() | 回傳Map中所有的鍵值對 |
| keySet() | 回傳Map中所有的key鍵 |
| values() | 回傳Map中所有的value值 |
| merge() | 添加鍵值對到Map中 |
| compute() | 對Map中指定key的值進行重新計算 |
| computeIfAbsent() | 對Map中指定key的值進行重新計算,如果該key不存在,則添加到Map中 |
| computeIfPresent() | 當key存在該Map時,對Map中指定key的值進行重新計算 |
與本節內容配套的視頻鏈接如下: 戳我直達視頻課程
5. 常用實作類
Java中有多個Map介面的實作類,接下來就給大家逐一簡單介紹一下,
5.1 HashMap
HashMap是Java中最常用的Map集合實作類,它基于哈希表實作,具有快速查找鍵值對的優點, HashMap的存盤方式是無序的,也就是說,遍歷HashMap集合時,得到的鍵值對順序是不確定的,下面是創建HashMap集合的代碼示例:
Map<String, Integer> hashMap = new HashMap<>();
5.2 TreeMap
TreeMap是Java中另一個常用的Map集合實作類,它基于紅黑樹實作,具有自動排序鍵值對的優點,TreeMap的存盤方式是有序的,也就是說,遍歷TreeMap集合時得到的鍵值對,是按照鍵的自然順序或指定比較器的順序排序的,下面是創建TreeMap集合的代碼示例:
Map<String, Integer> treeMap = new TreeMap<>();
5.3 LinkedHashMap
LinkedHashMap是Java中另一個Map集合實作類,它繼承自HashMap,并保持了插入順序,也就是說,遍歷LinkedHashMap集合時,得到的鍵值對的順序是按照插入順序排序的,下面是創建LinkedHashMap集合的代碼示例:
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
5.4 Hashtable
Hashtable是Java中另一個Map集合實作類,它與HashMap非常相似,但Hashtable是執行緒安全的,Hashtable的存盤方式是無序的,也就是說,遍歷Hashtable集合時,得到的鍵值對的順序是不確定的,下面是創建Hashtable集合的代碼示例:
Map<String, Integer> hashtable = new Hashtable<>();
需要注意的是,由于Hashtable是執行緒安全的,所以在多執行緒環境中使用Hashtable的性能可能會較低,現在一般是建議使用ConcurrentHashMap,
5.5 ConcurrentHashMap
ConcurrentHashMap是Java中的另一個Map集合實作類,它與Hashtable非常相似,但是ConcurrentHashMap是執行緒安全的,并且性能更高,ConcurrentHashMap的存盤方式是無序的,也就是說,遍歷ConcurrentHashMap集合時,得到的鍵值對的順序是不確定的,下面是創建ConcurrentHashMap集合的代碼示例:
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
需要注意的是,雖然ConcurrentHashMap是執行緒安全的,但仍然需要注意多執行緒環境下的并發問題,
二. HashMap集合
1. 簡介
HashMap繼承自AbstractMap類,實作了Map、Cloneable、java.io.Serializable介面,如下圖所示:
HashMap是基于散串列實作的雙列集合,它存盤的是key-value鍵值對映射,每個key-value鍵值對也被稱為一條Entry條目,其中的 key與 value,可以是任意的資料型別,其型別可以相同也可以不同,但一般情況下,key都是String型別,有時候也可以使用Integer型別;value可以是任何型別,并且在HashMap中,最多只能有一個記錄的key為null,但可以有多個value的值為null,
HashMap中這些鍵值對(Entry)會分散存盤在一個陣列當中,這個陣列就是HashMap的主體, 默認情況下,HashMap這個陣列中的每個元素的初始值都是null,但HashMap中最多只允許一條記錄的key為null,允許多條記錄的value為null,
另外HashMap是非執行緒安全的,即任一時刻如果有多個執行緒同時對HashMap進行寫操作,有可能會導致資料的不一致, 如果需要滿足執行緒的安全性要求,可以用 Collections.synchronizedMap() 方法使得HashMap具有執行緒安全的能力,或者使用ConcurrentHashMap來替代,
HashMap實作了Map介面,會根據key的hashCode值存盤資料,具有較快的訪問速度,另外HashMap是無序的,不會記錄插入元素的順序,我們一般是根據key的hashCode值來存取資料, 大多數情況下都可以直接根據key來定位到它所對應的值,因而HashMap有較快的訪問速度,但遍歷順序卻不確定,
2. 特點
根據上面的描述,把HashMap的核心特點總結如下:
- 基于哈希表實作,具有快速查找鍵值對的優點;
- HashMap的存盤方式是無序的;
- HashMap的key-value可以是任意型別,但key一般都是String型別,value型別任意;
- HashMap最多只能有一個記錄的key為null,但可以有多個value為null,
3. 常用操作
HashMap的使用方法和其他Map集合類似,主要包括添加元素、洗掉元素、獲取元素、遍歷元素等操作,接下來會詳細地給大家介紹HashMap集合的常用操作,
3.1 創建Map集合物件
創建HashMap物件的方式有多種,我們可以使用建構式,代碼如下:
// 使用建構式創建HashMap物件
Map<String, Integer> map1 = new HashMap<>();
3.2 添加元素
向HashMap集合添加元素的方式和List集合類似,也是使用put()方法,下面是向HashMap集合中添加元素的代碼示例:
public class Demo14 {
public static void main(String[] args) {
//HashMap
Map<String, String> map = new HashMap<>();
map.put("name","一一哥");
//map.put("name","壹哥");
map.put("age", "30");
map.put("sex", "男");
System.out.println("map="+map);
}
}
看到上面的代碼,有些小伙伴可能會問,如果我們往同一個Map中存盤兩個相同的key,但分別放入不同的value,這會有什么問題嗎?
其實我們在Map中,重復地放入 key-value 并不會有任何問題,但一個 key 只能關聯一個 value , 因為當我們呼叫put(K key, V value)方法時,如果放入的key已經存在,put()方法會回傳被洗掉的舊value,否則就會回傳null,所以Map中不會有重復的key,因為放入相同的key時,就會把原有key對應的value給替換掉,
3.3 洗掉元素
從HashMap集合中洗掉元素的方式也和List集合類似,使用remove()方法,下面是從HashMap集合中洗掉元素的代碼示例:
//從HashMap集合中移除元素
map.remove("age");
3.4 獲取元素
從HashMap集合中獲取元素的方式和List集合不同,需要使用鍵來獲取值,HashMap集合提供了兩種方法來獲取值,一種是使用get()方法,另一種是使用getOrDefault()方法,如果指定的鍵不存在,使用get()方法會回傳null,而getOrDefault()方法則會回傳指定的默認值,下面是從HashMap集合中獲取元素的代碼示例:
public class Demo15 {
public static void main(String[] args) {
//HashMap
Map<String, String> map = new HashMap<>();
map.put("name","一一哥");
map.put("age", "30");
map.put("sex", "男");
//根據key獲取指定的元素
String name = map.get("name");
String age = map.get("age");
//根據key獲取指定的元素,并設定默認值
String sex = map.getOrDefault("sex","男");
String height = map.getOrDefault("height","0");
System.out.println("[name]="+name+",[age]="+age+",[sex]="+sex+",[height]="+height);
}
}
3.5 遍歷元素
遍歷HashMap集合的方式和List集合不同,需要使用迭代器或者foreach回圈遍歷鍵值對,下面是遍歷HashMap集合的代碼示例:
/**
* @author 一一哥Sun
*/
public class Demo16 {
public static void main(String[] args) {
//HashMap
Map<String, String> map = new HashMap<>();
map.put("name","一一哥");
map.put("age", "30");
map.put("sex", "男");
//遍歷方式一:使用迭代器遍歷HashMap
//獲取集合中的entry條目集合
Set<Entry<String, String>> entrySet = map.entrySet();
//獲取集合中攜帶的Iterator迭代器物件
Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
//通過回圈進行迭代遍歷
while (iterator.hasNext()) {
//獲取每一個Entry條目物件
Map.Entry<String, String> entry = iterator.next();
//獲取條目中的key
String key = entry.getKey();
//獲取條目中的value
String value = https://www.cnblogs.com/qian-fen/archive/2023/05/25/entry.getValue();
System.out.println(key +" = " + value);
}
//遍歷方式二:用foreach回圈遍歷HashMap
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = https://www.cnblogs.com/qian-fen/archive/2023/05/25/entry.getValue();
System.out.println(key +" = " + value);
}
}
}
大家要注意,當我們使用Map時,任何依賴順序的邏輯都是不可靠的,比如,我們存入"A","B","C" 3個key,遍歷時,每個key會保證被遍歷一次且僅遍歷一次,但遍歷的順序完全沒有保證,甚至對于不同的JDK版本,相同的代碼遍歷輸出的順序都可能是不同的!所以我們在 遍歷Map時,要注意輸出的key是無序的!
3.6 判斷Map集合是否為空
判斷HashMap集合是否為空可以使用isEmpty()方法,如果Map集合為空,則回傳true,否則回傳false,下面是判斷HashMap集合是否為空的代碼示例:
// 判斷HashMap是否為空
boolean isEmpty = map.isEmpty();
3.7 獲取Map集合的大小
獲取HashMap集合的大小可以使用size()方法,回傳HashMap集合中鍵值對的數量,下面是獲取HashMap集合大小的代碼示例:
// 獲取HashMap的大小
int size = map.size();
3.8 判斷Map集合是否包含指定的鍵或值
如果我們想判斷HashMap集合是否包含指定的鍵或值,可以使用containsKey()和containsValue()方法,如果Map集合包含指定的鍵或值,則回傳true,否則回傳false,下面是判斷HashMap集合是否包含指定鍵或值的代碼示例:
// 判斷HashMap是否包含指定鍵
boolean containsKey = map.containsKey("name");
// 判斷HashMap是否包含指定值
boolean containsValue = https://www.cnblogs.com/qian-fen/archive/2023/05/25/map.containsValue("一一哥");
3.9 替換Map集合中的鍵值對
如果我們想替換HashMap集合中的鍵值對,可以使用replace()方法將指定鍵的值替換成新的值,如果指定的鍵不存在,則不進行任何操作,下面是替換HashMap集合中的鍵值對的代碼示例:
// 替換HashMap中的鍵值對
map.replace("name", "壹哥");
3.10 合并兩個Map集合
如果我們想合并兩個HashMap集合,可以使用putAll()方法,將另一個HashMap集合中所有的鍵值對,添加到當前的HashMap集合中,下面是合并兩個HashMap集合的代碼示例:
public class Demo16 {
public static void main(String[] args) {
//HashMap
Map<String, String> map1 = new HashMap<>();
map1.put("name","一一哥");
map1.put("age", "30");
map1.put("sex", "男");
// 創建另一個TreeMap集合
Map<String, String> map2 = new HashMap<>();
map2.put("height", "180");
map2.put("salary", "5w");
//將map1中的鍵值對添加到map2中
map2.putAll(map1);
System.out.println("map2="+map2);
}
}
3.11 獲取Map集合中所有的鍵或值
如果我們想獲取HashMap集合中所有的鍵或值,可以分別使用keySet()和values()方法,這兩個方法會回傳一個Set集合和一個Collection集合,它們包含了HashMap集合中所有的鍵或值,下面是獲取HashMap集合中所有鍵或值的代碼示例:
public class Demo18 {
public static void main(String[] args) {
//HashMap
Map<String, String> map = new HashMap<>();
map.put("name","一一哥");
map.put("age", "30");
map.put("sex", "男");
// 獲取HashMap中所有的鍵
Set<String> keySet = map.keySet();
for(String key : keySet) {
System.out.println("key="+key);
}
// 獲取HashMap中所有的值
Collection<String> values = map.values();
for(String value:values) {
System.out.println("value"+value);
}
}
}
4. 原理概述(重點)
作為開發時最常用的Map集合,HashMap在我們面試時被問到的概率非常高,尤其是關于其原理、資料結構、沖突解決、并發、擴容等相關的內容,更是經常被問到,
如果我們想要了解HashMap的底層原理,首先得知道HashMap的底層資料結構,而這個資料結構在不同的JDK版本中是不同的,我們可以把HashMap的底層資料結構分為兩大版本,即JDK 7及其以前的版本 和 JDK 8及其以后的版本, 大家注意,本文主要是結合JDK 8的原始碼,給大家講解HashMap的底層原理,
- 在JDK 7及其以前版本的HashMap中,其底層的資料結構是 陣列+鏈表 ;
- 而從JDK 8開始則采用 陣列+鏈表+紅黑樹 的資料結構,其中的 陣列是Entry型別或者說是Node型別陣列 ,
5. hash沖突解決機制
因為HashMap底層會使用Hash演算法來處理資料的存取,當資料非常多時就有一定的概率出現hash沖突,其解決程序如下,
- 當我們往HashMap中存盤資料時,首先會利用hash(key)方法 計算出key的hash值,再利用該 hash值 與 HashMap陣列的長度-1 進行 與運算,從而得到該key在陣列中對應的下標位置;
- 如果該位置上目前還沒有存盤資料,則直接將該key-value鍵值對資料存入到陣列中的這個位置;
- 如果該位置目前已經有了資料,則把新的資料存入到一個鏈表中;
- 當鏈表的長度超過閾值(JDK 8中該值為8)時,會將鏈表轉換為紅黑樹(轉換為紅黑樹還需要滿足其他的條件,鏈表長度達到閾值只是其中的一個條件),
通過這樣的機制,HashMap就解決了存值時可能產生的哈希沖突問題,并可以大大提高我們的查找效率,
當然由于HashMap極其重要,它的內容非常多,尤其是原理性的內容更多,但由于篇幅限制,不會在本文中過多地講解HashMap原理等的內容
三. Hashtable集合
1. 簡介
Hashtable也是Java中的一個Map集合,它與HashMap非常相似,但Hashtable是執行緒安全的,而HashMap不是執行緒安全的,Hashtable也可以存盤鍵值對,并可以通過鍵快速查找到對應的值,Hashtable的鍵和值也都可以是任何型別的物件,
因為Hashtable是執行緒安全的,因此適用于多執行緒環境,在多執行緒環境中,如果需要對Hashtable進行多個操作,需要使用synchronized關鍵字來保證執行緒安全,但需要我們注意的是,在多執行緒環境中使用Hashtable可能會影響性能,所以如果不需要保證執行緒安全,請盡量使用HashMap,
Hashtable集合的底層結構主要是陣列+鏈表,陣列是Entry陣列,鏈表也是用Entry來實作的 , 所以Hashtable的底層核心,其實也是基于哈希表,它使用哈希函式將鍵映射到哈希表中的一個位置,從而實作快速查找,另外Hashtable的存盤方式是無序的,也就是說,遍歷Hashtable集合得到的鍵值對的順序是不確定的,
2. 常用方法
Hashtable與HashMap類似,它的常用方法也與HashMap一樣:
- put(K key, V value):將指定的鍵值對存盤到Hashtable中,
- get(Object key):回傳指定鍵所對應的值,如果不存在該鍵則回傳null,
- remove(Object key):從Hashtable中移除指定鍵所對應的鍵值對,
- containsKey(Object key):判斷Hashtable中是否包含指定的鍵,
- containsValue(Object value):判斷Hashtable中是否包含指定的值,
- size():回傳Hashtable中鍵值對的數量,
- clear():移除Hashtable中的所有鍵值對,
3. 基本使用
下面是一個簡單的Hashtable集合示例,演示了如何創建Hashtable集合、存盤鍵值對、獲取值、遍歷集合等操作,
import java.util.Hashtable;
import java.util.Map;
public class Demo19 {
public static void main(String[] args) {
// 創建Hashtable集合
Map<String, Integer> hashtable = new Hashtable<>();
// 存盤鍵值對
hashtable.put("apple", 10);
hashtable.put("banana", 20);
hashtable.put("orange", 30);
// 獲取值
int value1 = hashtable.get("apple");
int value2 = hashtable.get("banana");
int value3 = hashtable.get("orange");
System.out.println("apple: " + value1);
System.out.println("banana: " + value2);
System.out.println("orange: " + value3);
// 移除鍵值對
hashtable.remove("orange");
// 遍歷集合
for (Map.Entry<String, Integer> entry : hashtable.entrySet()) {
String key = entry.getKey();
int value = https://www.cnblogs.com/qian-fen/archive/2023/05/25/entry.getValue();
System.out.println(key +": " + value);
}
// 清空集合
hashtable.clear();
}
}
其他方法的使用與HashMap基本一致,就不再一一細說了,
四. ConcurrentHashMap集合
1. 簡介
由于在多執行緒環境下,常規的HashMap可能會在陣列擴容及重哈希時出現 死回圈、臟讀 等執行緒安全問題,雖然有HashTable、Collections.synchronizedMap()可以取代HashMap進行并發操作,但因它們都是利用一個 全域的synchronized鎖 來同步不同執行緒之間的并發訪問,因此性能較差,
所以Java就從JDK 1.5版本開始,在J.U.C(java.util.concurrent并發包) 中引入了一個高性能的并發操作集合---ConcurrentHashMap(可以簡稱為CHM),該集合是一種執行緒安全的哈希表實作,相比Hashtable和SynchronizedMap,在多執行緒場景下具有更好的性能和可伸縮性,
并且ConcurrentHashMap集合在讀資料時不需要加鎖,寫資料時會加鎖,但鎖的粒度較小,不會對整個集合加鎖,而其內部又大量的利用了 volatile,final,CAS等lock-free(無鎖并發)技術,減少了鎖競爭對于性能的影響,具有更好的寫并發能力,但降低了對讀一致性的要求, 因此既保證了并發操作的安全性,又確保了讀、寫操作的高性能,可以說它的并發設計與實作都非常的精巧,
另外ConurrentHashMap中的key與value都不能為null,否則會產生空指標例外!
2. ConcurrentHashMap類關系
ConcurrentHashMap與HashMap具有相同的父類AbstractMap,他倆可以說是“親兄弟”,所以ConcurrentHashMap在一般的特性和使用上與HashMap基本是一致的,甚至很多底層原理也是相似的,
但兩者所在的包是不同的,ConcurrentHashMap是在java.util.concurrent包中,HashMap是在java.util包中,我們可以參考下圖:
以上所述的ConcurrentHashMap概念及特征,是不區分版本的,但實際上不同版本的ConcurrentHashMap內部實作差異很大,所以面試時經常會被問到不同版本之間的差異、各自特征,接下來會針對JDK 7 與 JDK 8 這兩個經典版本分別進行闡述,
3. 實作原理
3.1 并發控制機制
在JDK 7中,ConcurrentHashMap的核心機制是分段鎖(Segment),每個Segment內部維護了一個哈希表,且這個哈希表是執行緒安全的,而ConcurrentHashMap中的每個操作,都是先對所在的Segment進行加鎖,然后再執行具體的操作,
當多個執行緒對不同的Segment進行操作時,它們之間是并發的,當多個執行緒對同一個Segment進行操作時,它們會競爭鎖,但不會影響到其他Segment的操作,這種機制有效地降低了鎖的粒度,提高了并發訪問效率,
ConcurrentHashMap的另一個優點是支持可伸縮性,當需要增加ConcurrentHashMap的容量時,我們只需要增加Segment的數量即可,這種機制使得ConcurrentHashMap在高并發場景下具有良好的可伸縮性,
3.2 JDK 7版本的ConcurrentHashMap
在JDK 7版本中,ConcurrentHashMap和HashMap的設計思路其實是差不多的,但為了支持并發操作,做了一定的改進,比如ConcurrentHashMap中的資料是一段一段存放的,我們把這些分段稱之為Segment分段,在每個Segment分段中又有 陣列+鏈表 的資料結構,
默認情況下ConcurrentHashMap把主干分成了 16個Segment分段,并對每一段都單獨加鎖,我們把這種設計策略稱之為 "分段鎖",ConcurrentHashMap就是利用這種分段鎖機制進行并發控制的,JDK 7中ConcurrentHashMap的基本資料結構如下圖所示:
在理想狀態下,ConcurrentHashMap可以 同時支持16個執行緒 執行并發寫操作,以及任意數量的執行緒進行并發讀操作,在寫操作時,通過分段鎖技術,只對所操作的段加鎖而不會影響其它分段,且在讀操作時(幾乎)不需要加鎖,
3.3 JDK 8版本的ConcurrentHashMap
在JDK 8中,ConcurrentHashMap相對于JDK 7版本做了很大的改動,從實作的代碼量上就可以看出來,JDK 7中ConcurrentHashMap不到2000行代碼,而JDK 8中則有6000多行代碼,
JDK 8中放棄了臃腫的Segment設計,取而代之采用了 Node陣列 + 鏈表 + 紅黑樹 + CAS + Synchronized 技術來保證并發安全,實作并發控制操作,但是在ConcurrentHashMap的實作中保留了 Segment 的定義,這是為了 保證序列化時的兼容性,但并沒有任何結構上的用處,在JDK 8中用synchronized替換ReentrantLock的原因大致如下:
- 減少記憶體開銷: 如果使用ReentrantLock,就需要節點繼承AQS來獲得同步支持,這增加了記憶體開銷,而JDK 8中只有頭節點需要進行同步,
- 內部優化: synchronized是JVM直接支持的,JVM能夠在運行時作出相應的優化措施,比如 鎖粗化、鎖消除、鎖自旋等,
4. 基本使用
ConcurrentHashMap支持與HashMap相同的常用操作,如put、get、remove等,下面介紹一些常用操作,
4.1 插入元素
ConcurrentHashMap的put方法用于向集合中插入一個鍵值對,如果鍵已經存在,則會更新對應的值,下面是向ConcurrentHashMap中插入元素的示例:
import java.util.concurrent.ConcurrentHashMap;
public class Demo19 {
public static void main(String\[] args) {
// 創建ConcurrentHashMap集合
ConcurrentHashMap\<String, Integer> map = new ConcurrentHashMap<>();
// 插入元素
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 30);
// 輸出元素
System.out.println(map);
}
}
根據上面代碼的執行結果可知,ConcurrentHashMap中的鍵值對是無序的,
4.2 獲取元素
ConcurrentHashMap的get方法用于獲取指定鍵對應的值,如果鍵不存在,則回傳null,下面是一個從ConcurrentHashMap中獲取元素的示例:
import java.util.concurrent.ConcurrentHashMap;
public class Demo20 {
public static void main(String[] args) {
// 創建ConcurrentHashMap集合
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 插入元素
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 30);
// 獲取元素
Integer value = https://www.cnblogs.com/qian-fen/archive/2023/05/25/map.get("apple");
System.out.println(value);
}
}
4.3 洗掉元素
ConcurrentHashMap的remove方法用于從集合中洗掉指定鍵的元素,下面是一個從ConcurrentHashMap中洗掉元素的示例:
import java.util.concurrent.ConcurrentHashMap;
public class Demo21 {
public static void main(String[] args) {
// 創建ConcurrentHashMap集合
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 插入元素
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 30);
// 洗掉元素
map.remove("apple");
// 輸出元素
System.out.println(map);
}
}
五. 結語
至此,我們就把Map及其子類學習完了,現在你知道Map有什么特性及其用法了嗎?接下來我們簡單總結一下今天的重點吧:
- Map是一種映射表,可以通過key快速查找value;
- 可以通過 for each 遍歷 keySet() ,也可以通過 for each 遍歷 entrySet() ,直接獲取 key-value ;
- 最常用的Map是HashMap;
- Hashtable是執行緒安全的集合,底層結構主要是陣列+鏈表;
- ConcurrentHashMap可以良好地進行并發處理,是一種執行緒安全且高效的集合,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553412.html
標籤:其他
上一篇:Python集合 (set) 的增刪改查及 copy()方法
下一篇:返回列表
