深入淺析HashMap key和value能否為null
【一】HashMap
(1)結(jié)論:HashMap對(duì)象的key、value值均可為null
HashMap 的 key 和 value 都可以為 null 值。在 Java 中,HashMap 允許 null 作為 key 和 value 的值。當(dāng)插入 null 作為 key 時(shí),它將被存儲(chǔ)在 HashMap 的第一個(gè)位置上(即桶數(shù)組的第一個(gè)位置),而當(dāng)插入 null 作為 value 時(shí),它可以存儲(chǔ)在任何一個(gè)位置上。當(dāng)然,需要注意的是,由于 HashMap 是根據(jù) key 的哈希值來確定存儲(chǔ)位置的,所以插入 null 值作為 key 時(shí)需要格外小心,以避免出現(xiàn)哈希沖突導(dǎo)致的問題。
(2)key能否重復(fù)
key值不能重復(fù),若添加key相同的鍵值對(duì),后面的value會(huì)自動(dòng)覆蓋前面的value,但不會(huì)報(bào)錯(cuò)
(3)當(dāng)key為空時(shí),key的hash值為0,所以如果再設(shè)置一個(gè)值會(huì)對(duì)原有value進(jìn)行覆蓋
(4)HashMap是線程不安全的,他的key和value都可以為null
HashMap求hash值時(shí),并不是一上來就直接用key值求,他先進(jìn)行了一個(gè)判斷,如果為null,hash值為0。
對(duì)于get()方法
返回的是null,此時(shí)null值不知道是未找到還是對(duì)應(yīng)的value值。
這就出現(xiàn)了一個(gè)問題:當(dāng)A線程使用containsKey()進(jìn)行判斷時(shí),發(fā)現(xiàn)有這個(gè)元素,當(dāng)他調(diào)用get()取這個(gè)元素時(shí),B線程加入了進(jìn)來,B線程將這個(gè)元素移除掉了,此時(shí)A線程取得的值為null,A線程會(huì)以為自己取到了這個(gè)值,但實(shí)際上此時(shí)的null是未找到的null。這樣線程間就有可能出現(xiàn)安全問題。
以至于我們?cè)诙嗑€程情況下,使用的是currentHashMap存儲(chǔ)數(shù)據(jù),它的key和balue都是不能為null的。
【二】HashTable
(1)結(jié)論:HashTable對(duì)象的key、value值均不可為null
HashTable是線程安全的,HashTable對(duì)象的key、value值均不可為null。
當(dāng)我們調(diào)用put()方法時(shí):
為什么要一來就判斷value值不能為null呢?這就要看到get()方法:
發(fā)現(xiàn)沒有,如果value值能為null,那么我傳入對(duì)應(yīng)的key值,他找到了返回的是value值,也就是null,當(dāng)找不到時(shí),他也返回的是null。找到找不到返回值都是null,這怎么分辨?
所以,HashTable的key和value值都不能為null。
【三】ConcurrentHashMap
結(jié)論:key和value都不能為null
假定ConcurrentHashMap也可以存放value為null的值。那不管是HashMap還是ConcurrentHashMap調(diào)用map.get(key)的時(shí)候,如果返回了null,那么這個(gè)null,都有兩重含義:
(1)這個(gè)key從來沒有在map中映射過。
(2)這個(gè)key的value在設(shè)置的時(shí)候,就是null。
但是hashmap可以通過 containskey來確定到底是哪一個(gè)原因!
而多線程情況下,ConcurrentHashMap中的value不能為null
原因如下:
ConcurrentHashMap的使用場(chǎng)景為多線程。用反證法來推理,假設(shè)concurrentHashMap允許存放值為null的value。這時(shí)有A、B兩個(gè)線程。線程A調(diào)用concurrentHashMap.get(key)方法,返回為null,我們還是不知道這個(gè)null是沒有映射的null還是存的值就是null。
我們假設(shè)此時(shí)返回為null的真實(shí)情況就是因?yàn)檫@個(gè)key沒有在map里面映射過。那么我們可以用concurrentHashMap.containsKey(key)來驗(yàn)證我們的假設(shè)是否成立,我們期望的結(jié)果是返回false。
但是在我們調(diào)用concurrentHashMap.get(key)方法之后,containsKey方法之前,有一個(gè)線程B執(zhí)行了concurrentHashMap.put(key,null)的操作。那么我們調(diào)用containsKey方法返回的就是true了。這就與我們的假設(shè)的真實(shí)情況不符合了。也就是上面說的二義性。
上面也說了,hashmap可以key為null,但可以存在多個(gè)null嗎?
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
hashmap方法里面,當(dāng)k==null返回0,所以只要key為null就將Node插入到索引為0的桶當(dāng)中,那下一個(gè)null來了怎么辦?源碼當(dāng)中寫了,首先判斷這個(gè)存在的節(jié)點(diǎn),如果它們的hashcode相等,下一步判斷key是否相同,這里判斷用到了||,就是地址一樣(都是null也成了),或者equals相同也可以,就進(jìn)行替換,所以得出結(jié)論:hashmap 當(dāng)中 key為null的只有一個(gè)?。?!
【四】測(cè)試代碼
public class Test { public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>();//HashMap對(duì)象 Map<String, String> tableMap = new Hashtable<String, String>();//HashTable對(duì)象 map.put(null, null); System.out.println("hashMap的[key]和[value]均可以為null:" + map.get(null)); try { tableMap.put(null, "3"); System.out.println(tableMap.get(null)); } catch (Exception e) { System.out.println("【ERROR】:hashTable的[key]不能為null"); } try { tableMap.put("3", null); System.out.println(tableMap.get("3")); } catch (Exception e) { System.out.println("【ERROR】:hashTable的[value]不能為null"); } } }
import java.util.HashMap; import java.util.Hashtable; public class TestMap { public static void main(String[] args){ HashMap<Integer,Integer> map = new HashMap<>(); System.out.println(map.containsKey(null)); System.out.println(map.get(null)); //驗(yàn)證 HashMap的key和value都可以為null //當(dāng)key為空時(shí),key的hash值為0 map.put(null,null); System.out.println(map.containsKey(null)); System.out.println(map.get(null)); //當(dāng)key為空時(shí),key的hash值為0,所以如果再設(shè)置一個(gè)值會(huì)對(duì)原有value進(jìn)行覆蓋 map.put(null,123); System.out.println(map.containsKey(null)); System.out.println(map.get(null)); //驗(yàn)證 Hashtable Hashtable<Integer,Integer> hashtable = new Hashtable<>(); System.out.println(hashtable.containsKey(null)); System.out.println(hashtable.get(null)); //HashTable是線程安全的,key和value都不可以為null //HashMap是線程不安全的,他的key和value都可以為null //驗(yàn)證 HashMap的key和value都可以為null //當(dāng)key為空時(shí),key的hash值為0 hashtable.put(null,null); System.out.println(hashtable.containsKey(null)); System.out.println(hashtable.get(null)); } }
執(zhí)行效果
false
null
true
null
true
123
Exception in thread "main" java.lang.NullPointerException
at java.util.Hashtable.containsKey(Hashtable.java:336)
at com.itheima.test.TestMap.main(TestMap.java:31)
【五】底層代碼分析
【1】Hashtable
public synchronized V put(K key, V value) { // 確保value不為空。這句代碼過濾掉了所有value為null的鍵值對(duì)。因此Hashtable不能 // 存儲(chǔ)value為null的鍵值對(duì) if (value == null) { throw new NullPointerException(); } // 確保key在table數(shù)組中尚未存在。 Entry<?,?> tab[] = table; int hash = key.hashCode(); //在此處計(jì)算key的hash值,如果此處key為null,則直接拋出空指針異常。 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
Hashtable的源碼可以看出,如果value=null直接拋出空指針異常;而使用key.hashCode()不允許key=null,所以無論是key還是value都不能是null。而在HashMap中并沒有這樣的限制,key和value允許使用null。
【2】HashMap
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
HashMap計(jì)算key的hash值時(shí)調(diào)用單獨(dú)的方法,在該方法中會(huì)判斷key是否為null,如果是則返回0;
到此這篇關(guān)于深入淺析HashMap key和value能否為null的文章就介紹到這了,更多相關(guān)HashMap key和value能否為null內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于JavaMail的Java實(shí)現(xiàn)簡單郵件發(fā)送功能
這篇文章主要為大家詳細(xì)介紹了基于JavaMail的Java實(shí)現(xiàn)簡單郵件發(fā)送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Springboot項(xiàng)目啟動(dòng)不加載resources目錄下的文件問題
這篇文章主要介紹了Springboot項(xiàng)目啟動(dòng)不加載resources目錄下的文件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Spring?Boot項(xiàng)目抵御XSS攻擊實(shí)戰(zhàn)過程
XSS攻擊又稱跨站腳本攻擊,通常指利用網(wǎng)頁開發(fā)時(shí)留下的漏洞,通過巧妙的方法注入惡意指令代碼到網(wǎng)頁,使用戶加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁程序,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot項(xiàng)目抵御XSS攻擊的相關(guān)資料,需要的朋友可以參考下2022-11-11Java8加java10等于Java18的版本查看及特性詳解
這篇文章主要為大家介紹了Java?8加java10等于Java18的各個(gè)版本要點(diǎn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06springboot多模塊多環(huán)境配置文件問題(動(dòng)態(tài)配置生產(chǎn)和開發(fā)環(huán)境)
這篇文章主要介紹了springboot多模塊多環(huán)境配置文件問題(動(dòng)態(tài)配置生產(chǎn)和開發(fā)環(huán)境),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04springboot配置文件中使用${}注入值的兩種方式小結(jié)
這篇文章主要介紹了springboot配置文件中使用${}注入值的兩種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03