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