主頁 > 後端開發 > 詳細解讀Java中Map集合的底層原理(干貨+原始碼解讀)

詳細解讀Java中Map集合的底層原理(干貨+原始碼解讀)

2023-05-26 08:11:48 後端開發

本文將為大家詳細講解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類則可以對鍵物件進行排序,

image.png

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介面的實作類,接下來就給大家逐一簡單介紹一下,

image.png

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介面,如下圖所示:

image.png

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)時,會將鏈表轉換為紅黑樹(轉換為紅黑樹還需要滿足其他的條件,鏈表長度達到閾值只是其中的一個條件),

image.png

通過這樣的機制,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包中,我們可以參考下圖:

image.png

以上所述的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的基本資料結構如下圖所示:

image.png

在理想狀態下,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()方法

下一篇:返回列表

標籤雲
其他(159700) Python(38169) JavaScript(25452) Java(18129) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7211) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5341) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1944) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 詳細解讀Java中Map集合的底層原理(干貨+原始碼解讀)

    本文將為大家詳細講解Java中的Map集合,這是我們進行開發時經常用到的知識點,也是大家在學習Java中很重要的一個知識點,更是我們在面試時有可能會問到的問題。文章較長,干貨滿滿,建議大家收藏慢慢學習。文末有本文重點總結,主頁有全系列文章分享。技術類問題,歡迎大家和我們一起交流討論! ......

    uj5u.com 2023-05-26 08:11:48 more
  • Python集合 (set) 的增刪改查及 copy()方法

    集合是無序的,不重復的資料集合,它里面的元素是可哈希的(不可變型別),但是集合本身是不可哈希(所以集合做不了字典的鍵)的。 以下是集合最重要的兩點: 1、去重,把一個串列變成集合,就自動去重了。 2、關系測驗,測驗兩組資料之前的交集、差集、并集等關系。 ### 一、集合的創建 ```python s ......

    uj5u.com 2023-05-26 08:11:43 more
  • 【編程日記】搭建PyCharm集成開發環境

    # 0.相關確定 本教程使用的版本號為專業版PyCharm 2022.3.2,如果您是初學者,為了更好的學習本教程,避免不必要的麻煩,請您下載使用與本教程一致的版本號。 # 1.PyCharm的下載 官網下載:https://www.jetbrains.com/pycharm/download/ot ......

    uj5u.com 2023-05-26 08:06:24 more
  • 如何證明Servlet是單例的?

    Servlet是web體系里面最重要的部分,下面羅列幾道常見的面試題,小伙伴們一定要好好記住哈。 1.Servlet是單例的嗎,如何證明? Servlet一般都是單例的,并且是多執行緒的。如何證明Servlet是單例模式呢?很簡單,重寫Servlet的init方法,或者添加一個構造方法。然后,在web ......

    uj5u.com 2023-05-26 08:01:14 more
  • Rocksdb原理簡介

    Rocksdb作為當下nosql中性能的代表被各個存盤組件(mysql、tikv、pmdk、bluestore)作為存盤引擎底座,其基于LSM tree的核心存盤結構(將隨機寫通過資料結構轉化為順序寫)來提供高性能的寫吞吐時保證了讀性能。同時大量的并發性配置來降低compaction的影響。 ......

    uj5u.com 2023-05-26 07:55:59 more
  • 用go封裝一下封禁功能

    本篇為[用go設計開發一個自己的輕量級登錄庫/框架吧]的封禁業務篇,會講講封禁業務的實作,給庫/框架增加新的功能。原始碼:https://github.com/weloe/token-go ......

    uj5u.com 2023-05-26 07:50:53 more
  • 它來了!真正的 python 多執行緒

    哈嘍大家好,我是咸魚 幾天前,IBM 工程師 Martin Heinz 發文表示 python 3.12 版本回引入"Per-Interpreter GIL”,有了這個 Per-Interpreter 全域解釋器鎖,python 就能實作真正意義上的并行/并發 我們知道,python 的多執行緒/行程 ......

    uj5u.com 2023-05-26 07:50:49 more
  • ThreadLocal的應用及原理

    ## 1. ThreadLocal 是什么 JDK 對`ThreadLocal`的描述為: > 此類提供執行緒區域變數。這些變數與普通變數的不同之處在于,每個訪問一個變數的執行緒(通過其get或set方法)都有自己的、獨立初始化的變數副本。ThreadLocal 實體通常是類中的私有靜態欄位,這些欄位希 ......

    uj5u.com 2023-05-26 07:45:35 more
  • Java的CompletableFuture,Java的多執行緒開發

    # 三、Java8的CompletableFuture,Java的多執行緒開發 ## 1、CompletableFuture的常用方法 - 以后用到再加 ```properties runAsync() :開啟異步(創建執行緒執行任務),無回傳值 supplyAsync() :開啟異步(創建執行緒執行任務 ......

    uj5u.com 2023-05-26 07:35:06 more
  • Maven的核心解壓與配置

    ? # Maven的核心解壓與配置 @[toc] ## 1. Maven 官網地址 首頁:[Maven – Welcome to Apache Maven(opens new window)](https://maven.apache.org/) ![在這里插入圖片描述](https://img20 ......

    uj5u.com 2023-05-26 07:29:23 more