HashMap 底層實現原理及常見面試題

hashmap的特性

HashMap可以接受null鍵值和值,而Hashtable則不能;HashMap是非synchronized;HashMap很快;以及HashMap儲存的是鍵值對等等。

hashmap的工作原理

HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取對象。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode(也就是數組的key),然後找到bucket位置來儲存值對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每個鏈表節點中儲存鍵值對對象。

兩個對象的hashcode相同會發生什麼?

因爲hashcode相同,所以它們的bucket位置相同,‘碰撞’會發生。因爲HashMap使用鏈表存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。

造成hash衝突的原因

HashMap使用一個Entry數組保存key、value數據,當一對key、value被加入時,會通過一個hash算法得到數組的下標index,算法很簡單,根據key的hash值,對數組的大小取模 hash & (length-1),並把結果插入數組該位置,如果該位置上已經有元素了,就說明存在hash衝突,這樣會在index位置生成鏈表。

如何解決hash碰撞

當我們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,找到bucket位置之後,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。

如何減少hash碰撞

一些優秀的開發者會指出使用不可變的、聲明作final的對象,並且採用合適的equals()和hashCode()方法的話,將會減少碰撞的發生,提高效率。不可變性使得能夠緩存不同鍵的hashcode,這將提高整個獲取對象的速度,使用String,Interger這樣的wrapper類作爲鍵是非常好的選擇。

爲什麼String, Interger這樣的wrapper類適合作爲鍵?

String, Interger這樣的wrapper類作爲HashMap的鍵是再適合不過了,而且String最爲常用。因爲String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因爲爲了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其他的優點如線程安全。

重新調整HashMap的大小

默認的時候,和其負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket它集合類(如ArrayList等)一樣,將會創建原來HashMap大小的兩倍的bucket數組,來重新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫作rehashing,因爲它調用hash方法找到新的bucket位置。

你可能會問如果數組擴容了,它的下標不就變了嗎?

確實變了,需要重新計算它的下標了,然後把它插入到新的更大的數組裏
這就是爲什麼Node類中要存儲hash值
這就是爲什麼HashMap是沒有順序的
這就是爲什麼說擴容是非常消耗性能的

多線程下hashmap問題

因爲是鏈表結構,那麼就很容易形成閉合的鏈路,這樣在循環的時候只要有線程對這個HashMap進行get操作就會產生死循環。但是,我好奇的是,這種閉合的鏈路是如何形成的呢。在單線程情況下,只有一個線程對HashMap的數據結構進行操作,是不可能產生閉合的迴路的。那就只有在多線程併發的情況下才會出現這種情況,那就是在put操作的時候,如果size>initialCapacity*loadFactor,那麼這時候HashMap就會進行rehash操作,隨之HashMap的結構就會發生翻天覆地的變化。很有可能就是在兩個線程在這個時候同時觸發了rehash操作,產生了閉合的迴路。

線程安全性

HashMap是線程不安全的,在多線程的情況下,儘量不要使用HashMap(雖然它的性能很好),而使用線程安全的ConcurrentHashMap

紅黑樹應用

jdk1.8引入了紅黑樹處理hashMap,當鏈表長度大於8時,自動轉爲紅黑樹.

小結

擴容是一個特別耗性能的操作,所以在使用HashMap的時候,最好估算一下Map的大小,初始化的時候給一個大致的數值,避免Map頻繁擴容。
JDK1.8引入紅黑樹大大優化了HashMap的性能。
HashMap是線程不安全的,不要在併發的環境中同時操作HashMap,建議使用ConcurrentHashMap
負載因子是可以修改的,也可以大於1,但是建議不要輕易修改,除非情況非常特殊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章