Java中的HashMap和ConcurrentHashMap區(qū)別和適用場景
HashMap和ConcurrentHashMap在對null值的處理、線程安全性、性能等方面存在顯著的區(qū)別。HashMap允許鍵和值為null,適用于單線程環(huán)境下的數(shù)據(jù)存儲和查詢場景,具有較高的性能和簡單的使用方式;而ConcurrentHashMap不允許鍵和值為null,適用于多線程環(huán)境下的數(shù)據(jù)存儲和查詢場景,具有線程安全性和較高的并發(fā)性能。

1. 對null值的處理
1.1 HashMap對null值的處理
HashMap允許鍵(key)和值(value)都為null。這種設(shè)計(jì)使得HashMap在某些場景下更加靈活。例如,在處理一些可能存在空值的數(shù)據(jù)源時(shí),可以直接將數(shù)據(jù)存儲到HashMap中,而不需要額外的非空判斷或轉(zhuǎn)換。以下是一個(gè)簡單的示例代碼:
HashMap<String, Object> map = new HashMap<>();
map.put(null, null); // 正常執(zhí)行,key 和 value 都為 null
if (map.containsKey(null)) {
System.out.println("存在 null");
} else {
System.out.println("不存在 null");
}執(zhí)行上述代碼,控制臺會輸出“存在 null”,表明HashMap成功地將null作為鍵和值存儲,并且可以通過containsKey(null)方法準(zhǔn)確地判斷出null鍵的存在。
1.2 ConcurrentHashMap對null值的處理
與HashMap不同,ConcurrentHashMap不允許鍵(key)和值(value)為null。如果嘗試將null作為鍵或值插入到ConcurrentHashMap中,會拋出NullPointerException異常。以下是兩個(gè)示例代碼:
ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>(); concurrentHashMap.put(null, "javacn.site"); // 拋出 NullPointerException
String key = "www.Javacn.site"; ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>(); concurrentHashMap.put(key, null); // 拋出 NullPointerException
在上述兩個(gè)代碼片段中,無論是將null作為鍵還是值插入到ConcurrentHashMap中,都會導(dǎo)致程序異常終止,并拋出NullPointerException異常。
1.3 為什么ConcurrentHashMap不能插入null?
要理解ConcurrentHashMap為什么不能插入null值,我們需要從其源碼層面進(jìn)行分析。以下是ConcurrentHashMap添加元素時(shí)的部分核心源碼:
// 添加 key 和 value
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 如果 key 或 value 為 null 的話直接拋出空指針異常
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
// 忽略其他代碼......
}從上述源碼可以看出,在putVal方法的第一行,ConcurrentHashMap就對key和value進(jìn)行了null檢查,如果發(fā)現(xiàn)它們中的任何一個(gè)為null,就會直接拋出NullPointerException異常。這種設(shè)計(jì)是ConcurrentHashMap的一個(gè)明確的約束條件,旨在避免因null值引起的潛在問題。
1.4 更深層次的原因
那么,為什么ConcurrentHashMap的實(shí)現(xiàn)源碼中要明確禁止key或value為null呢?這要從ConcurrentHashMap的使用場景和并發(fā)環(huán)境下的特殊性來分析。
1.4.1 二義性問題
在并發(fā)環(huán)境下,如果允許ConcurrentHashMap的key或value為null,就會存在經(jīng)典的“二義性問題”。二義性問題指的是代碼或表達(dá)式存在多種理解或解釋,導(dǎo)致程序的含義不明確或模糊。對于ConcurrentHashMap來說,null值的二義性主要體現(xiàn)在以下兩個(gè)方面:
- null作為具體值:當(dāng)我們將null作為值存儲到ConcurrentHashMap中時(shí),null表示的就是一個(gè)具體的“null”值狀態(tài)。例如,某個(gè)業(yè)務(wù)場景中,某個(gè)屬性的值可能就是null,表示該屬性沒有具體的值或者不適用。
- null表示“沒有”:在某些情況下,null還可以表示“沒有”的意思,即某個(gè)鍵在ConcurrentHashMap中根本沒有對應(yīng)的值。例如,當(dāng)我們查詢ConcurrentHashMap時(shí),如果返回null,可能是因?yàn)樵撴I從未被插入過,或者該鍵對應(yīng)的值確實(shí)就是null。
如果ConcurrentHashMap允許插入null值,那么在并發(fā)環(huán)境下,當(dāng)我們查詢某個(gè)鍵時(shí),得到的null值就無法明確區(qū)分是上述哪種情況,從而導(dǎo)致二義性問題。
1.4.2 HashMap的可證偽性
相比之下,HashMap允許插入null值,但它不怕二義性問題的原因在于,HashMap是為單線程環(huán)境設(shè)計(jì)的。在單線程環(huán)境下,二義性問題是可被證明真?zhèn)蔚?。例如,?dāng)我們給HashMap的key設(shè)置為null時(shí),可以通過hashMap.containsKey(key)的方法來區(qū)分這個(gè)null值到底是存入的null,還是壓根不存在的null。因?yàn)閱尉€程環(huán)境下,數(shù)據(jù)的修改和查詢是順序執(zhí)行的,不會受到其他線程的干擾,所以二義性問題可以被明確地解決。
1.4.3 ConcurrentHashMap的不可證偽性
而ConcurrentHashMap是為多線程環(huán)境設(shè)計(jì)的,多線程下的二義性問題是不能被證明真?zhèn)蔚?。因?yàn)樵谧C明二義性問題的同時(shí),可能會有其他線程影響執(zhí)行結(jié)果,導(dǎo)致結(jié)果不準(zhǔn)確。例如,當(dāng)ConcurrentHashMap未設(shè)置key為null時(shí),可能會出現(xiàn)以下場景:一個(gè)線程A調(diào)用了concurrentHashMap.containsKey(key),期望返回的結(jié)果是false,但在調(diào)用該方法之后,未返回結(jié)果之前,線程B又調(diào)用了concurrentHashMap.put(key, null)存入了null值,那么線程A最終返回的結(jié)果就是true了。這個(gè)結(jié)果與線程A之前預(yù)想的false完全不一樣,這就是不能被證偽的二義性問題。
為了避免這種復(fù)雜的二義性問題,ConcurrentHashMap選擇在源碼中明確禁止null值作為key或value,從而簡化了并發(fā)環(huán)境下的數(shù)據(jù)管理邏輯,確保了數(shù)據(jù)的一致性和準(zhǔn)確性。
2. 線程安全性
2.1 HashMap的線程安全性
HashMap是非線程安全的,這意味著在多線程環(huán)境下使用HashMap時(shí),可能會遇到各種并發(fā)問題,如數(shù)據(jù)丟失、數(shù)據(jù)重復(fù)、死鎖等。具體來說,HashMap的線程不安全性主要體現(xiàn)在以下幾個(gè)方面:
- 數(shù)據(jù)丟失:當(dāng)多個(gè)線程同時(shí)對HashMap進(jìn)行插入操作時(shí),可能會出現(xiàn)數(shù)據(jù)丟失的情況。例如,兩個(gè)線程同時(shí)插入相同的鍵但不同的值,最終可能會導(dǎo)致只有一個(gè)值被成功插入,而另一個(gè)值被覆蓋。
- 數(shù)據(jù)重復(fù):在某些情況下,多個(gè)線程可能會插入重復(fù)的數(shù)據(jù)。例如,當(dāng)多個(gè)線程同時(shí)檢查某個(gè)鍵是否存在,并且發(fā)現(xiàn)該鍵不存在時(shí),它們可能會同時(shí)插入相同的鍵值對,導(dǎo)致數(shù)據(jù)重復(fù)。
- 死鎖:在某些復(fù)雜的操作中,如擴(kuò)容時(shí),HashMap可能會出現(xiàn)死鎖問題。擴(kuò)容操作需要重新計(jì)算所有鍵的哈希值,并將它們重新分配到新的桶數(shù)組中。如果多個(gè)線程同時(shí)進(jìn)行擴(kuò)容操作,可能會導(dǎo)致死鎖,從而影響程序的正常運(yùn)行。
2.2 ConcurrentHashMap的線程安全性
與HashMap不同,ConcurrentHashMap是線程安全的,它通過多種機(jī)制來保證在多線程環(huán)境下的安全性。以下是ConcurrentHashMap實(shí)現(xiàn)線程安全性的主要機(jī)制:
- 分段鎖(Segment):在早期的版本中,ConcurrentHashMap使用分段鎖來實(shí)現(xiàn)線程安全。它將整個(gè)哈希表分為多個(gè)段(Segment),每個(gè)段相當(dāng)于一個(gè)小的哈希表,并且每個(gè)段都有自己的鎖。當(dāng)對ConcurrentHashMap進(jìn)行操作時(shí),只需要鎖定相關(guān)的段,而不需要鎖定整個(gè)哈希表。這樣可以顯著提高并發(fā)性能,因?yàn)槎鄠€(gè)線程可以同時(shí)對不同的段進(jìn)行操作,而不會相互干擾。
- CAS操作:ConcurrentHashMap使用了Compare-And-Swap(CAS)操作來保證某些操作的原子性。CAS是一種無鎖的非阻塞算法,它通過比較內(nèi)存中的值與預(yù)期值是否相等,如果相等,則將內(nèi)存中的值更新為新值。例如,在插入鍵值對時(shí),ConcurrentHashMap會使用CAS操作來更新節(jié)點(diǎn)的引用,從而保證插入操作的原子性。
- 鎖分離:ConcurrentHashMap將讀操作和寫操作分開處理,讀操作不需要加鎖,而寫操作則需要加鎖。這種鎖分離機(jī)制可以提高并發(fā)性能,因?yàn)槎鄠€(gè)線程可以同時(shí)進(jìn)行讀操作,而不會受到寫操作的影響。只有當(dāng)進(jìn)行寫操作時(shí),才會對相關(guān)部分加鎖,從而保證數(shù)據(jù)的一致性和安全性。
2.3 線程安全性對性能的影響
線程安全性對性能有著直接的影響。對于HashMap來說,由于其非線程安全的特性,在單線程環(huán)境下可以提供較高的性能,因?yàn)椴恍枰M(jìn)行額外的鎖操作和同步處理。然而,在多線程環(huán)境下,HashMap的性能會受到嚴(yán)重影響,因?yàn)樾枰~外的同步機(jī)制來保證線程安全,如使用Collections.synchronizedMap方法對HashMap進(jìn)行包裝,或者在使用HashMap時(shí)手動進(jìn)行同步處理。
相比之下,ConcurrentHashMap由于其線程安全的特性,在多線程環(huán)境下可以提供較高的性能。它通過分段鎖、CAS操作和鎖分離等機(jī)制,減少了鎖的粒度和鎖的爭用,從而提高了并發(fā)性能。在多線程環(huán)境下,多個(gè)線程可以同時(shí)對ConcurrentHashMap進(jìn)行讀寫操作,而不會出現(xiàn)嚴(yán)重的性能瓶頸。當(dāng)然,在單線程環(huán)境下,ConcurrentHashMap的性能可能會略低于HashMap,因?yàn)槠鋬?nèi)部的線程安全機(jī)制會帶來一定的開銷。
3. 性能比較
3.1 時(shí)間復(fù)雜度
從時(shí)間復(fù)雜度的角度來看,HashMap和ConcurrentHashMap在大多數(shù)操作上的時(shí)間復(fù)雜度都是O(1),即常數(shù)時(shí)間復(fù)雜度。這是因?yàn)樗鼈兌际腔诠1韺?shí)現(xiàn)的,通過計(jì)算鍵的哈希值來快速定位對應(yīng)的桶(bucket),從而實(shí)現(xiàn)快速的插入、刪除和查找操作。
然而,在某些情況下,時(shí)間復(fù)雜度可能會退化到O(n),即線性時(shí)間復(fù)雜度。例如,當(dāng)哈希表中出現(xiàn)大量哈希沖突時(shí),即多個(gè)鍵的哈希值相同或相近,導(dǎo)致它們被分配到同一個(gè)桶中,此時(shí)需要遍歷桶中的鏈表或紅黑樹來找到對應(yīng)的鍵值對,時(shí)間復(fù)雜度會退化到O(n)。不過,這種情況在正常情況下是較少出現(xiàn)的,因?yàn)榱己玫墓:瘮?shù)和合理的擴(kuò)容機(jī)制可以有效地減少哈希沖突的發(fā)生。
3.2 并發(fā)性能
在并發(fā)性能方面,ConcurrentHashMap顯然優(yōu)于HashMap。如前所述,ConcurrentHashMap通過分段鎖、CAS操作和鎖分離等機(jī)制,減少了鎖的粒度和鎖的爭用,從而提高了并發(fā)性能。在多線程環(huán)境下,多個(gè)線程可以同時(shí)對ConcurrentHashMap進(jìn)行讀寫操作,而不會出現(xiàn)嚴(yán)重的性能瓶頸。
相比之下,HashMap在多線程環(huán)境下需要額外的同步機(jī)制來保證線程安全,這會大大降低其并發(fā)性能。例如,使用Collections.synchronizedMap方法對HashMap進(jìn)行包裝時(shí),會對所有的操作進(jìn)行同步處理,導(dǎo)致多個(gè)線程在操作HashMap時(shí)需要排隊(duì)等待,從而出現(xiàn)嚴(yán)重的性能瓶頸。
3.3 內(nèi)存占用
從內(nèi)存占用的角度來看,ConcurrentHashMap通常會比HashMap占用更多的內(nèi)存。這是因?yàn)镃oncurrentHashMap為了實(shí)現(xiàn)線程安全,需要額外的數(shù)據(jù)結(jié)構(gòu)和鎖機(jī)制。例如,在早期的版本中,ConcurrentHashMap使用分段鎖時(shí),每個(gè)段都需要占用一定的內(nèi)存空間。此外,ConcurrentHashMap在擴(kuò)容時(shí)也需要進(jìn)行更多的內(nèi)存分配和數(shù)據(jù)遷移操作,從而增加了內(nèi)存的占用。
相比之下,HashMap的內(nèi)存占用相對較少,因?yàn)樗恍枰~外的鎖機(jī)制和復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。不過,隨著ConcurrentHashMap的不斷優(yōu)化,其內(nèi)存占用情況也在逐漸改善。例如,在Java 8及以后的版本中,ConcurrentHashMap采用了新的數(shù)據(jù)結(jié)構(gòu)和優(yōu)化算法,減少了內(nèi)存的占用。
4. 使用場景
4.1 HashMap的使用場景
HashMap適用于單線程環(huán)境下的數(shù)據(jù)存儲和查詢場景。由于其非線程安全的特性,在單線程環(huán)境下可以提供較高的性能,且使用起來相對簡單。以下是一些典型的使用場景:
- 數(shù)據(jù)緩存:在單線程應(yīng)用中,可以使用HashMap來緩存一些數(shù)據(jù),如數(shù)據(jù)庫查詢結(jié)果、計(jì)算結(jié)果等,以提高程序的執(zhí)行效率。
- 對象映射:在對象之間建立映射關(guān)系時(shí),可以使用HashMap來存儲對象的鍵值對。例如,在一個(gè)游戲中,可以使用HashMap來存儲玩家的ID和對應(yīng)的玩家對象之間的映射關(guān)系。
- 參數(shù)傳遞:在方法調(diào)用時(shí),可以使用HashMap來傳遞一組參數(shù),每個(gè)參數(shù)的名稱作為鍵,參數(shù)的值作為值。這種方式可以簡化參數(shù)的傳遞過程,使代碼更加靈活和可讀。
4.2 ConcurrentHashMap的使用場景
ConcurrentHashMap適用于多線程環(huán)境下的數(shù)據(jù)存儲和查詢場景。由于其線程安全的特性,在多線程環(huán)境下可以提供較高的性能和數(shù)據(jù)一致性保證。以下是一些典型的使用場景:
- 共享數(shù)據(jù)存儲:在多線程應(yīng)用中,可以使用ConcurrentHashMap來存儲多個(gè)線程需要共享的數(shù)據(jù)。例如,在一個(gè)Web應(yīng)用中,可以使用ConcurrentHashMap來存儲用戶的會話信息,多個(gè)線程可以同時(shí)訪問和修改這些會話信息,而不會出現(xiàn)數(shù)據(jù)不一致的問題。
- 緩存實(shí)現(xiàn):在需要實(shí)現(xiàn)線程安全的緩存時(shí),可以使用ConcurrentHashMap作為底層數(shù)據(jù)結(jié)構(gòu)。例如,在一個(gè)分布式系統(tǒng)中,可以使用ConcurrentHashMap來緩存一些共享資源的狀態(tài)信息,多個(gè)節(jié)點(diǎn)可以同時(shí)訪問和更新這些狀態(tài)信息。
- 并發(fā)統(tǒng)計(jì):在進(jìn)行并發(fā)統(tǒng)計(jì)時(shí),可以使用ConcurrentHashMap來存儲統(tǒng)計(jì)結(jié)果。例如,在一個(gè)日志分析系統(tǒng)中,可以使用ConcurrentHashMap來統(tǒng)計(jì)不同日志級別的出現(xiàn)次數(shù),多個(gè)線程可以同時(shí)對日志進(jìn)行分析和統(tǒng)計(jì),而不會出現(xiàn)統(tǒng)計(jì)結(jié)果不準(zhǔn)確的問題。
5. 總結(jié)
在實(shí)際開發(fā)中,我們需要根據(jù)具體的使用場景和需求來選擇合適的Map實(shí)現(xiàn)類。如果是在單線程環(huán)境下,且需要處理可能存在null值的數(shù)據(jù),可以選擇使用HashMap;如果是在多線程環(huán)境下,需要保證數(shù)據(jù)的線程安全性和一致性,可以選擇使用ConcurrentHashMap。此外,還可以根據(jù)性能要求、內(nèi)存占用等因素來綜合考慮,以選擇最適合的Map實(shí)現(xiàn)類來滿足實(shí)際需求。
到此這篇關(guān)于Java中的HashMap和ConcurrentHashMap區(qū)別和適用場景的文章就介紹到這了,更多相關(guān)HashMap和ConcurrentHashMap的區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用Mybatis-Generator配置過程詳解
這篇文章主要介紹了SpringBoot使用Mybatis-Generator配置過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
基于SpringBoot+JWT 實(shí)現(xiàn)Token登錄認(rèn)證與登錄人信息查詢功能
本文給大家介紹基于SpringBoot+JWT實(shí)現(xiàn)Token登錄認(rèn)證與登錄人信息查詢功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值2026-03-03
Java?Stream函數(shù)式編程管道流結(jié)果處理
這篇文章主要為大家介紹了Java?Stream函數(shù)式編程管道流結(jié)果處理的示例過程解析需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
Java后端Tomcat實(shí)現(xiàn)WebSocket實(shí)例教程
WebSocket protocol 是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信(full-duplex)。一開始的握手需要借助HTTP請求完成握手。本文給大家介紹Java后端Tomcat實(shí)現(xiàn)WebSocket實(shí)例教程,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋
這篇文章主要介紹了springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

