HashMap底層數據結構?jdk1.8算法優化,hash沖突,擴容等問題


面試必備系列不會長篇理論求證,直接上答案,僅供參考,不喜勿噴。

1、能說說HashMap的底層原理嗎?

HashMap<String,String> map = new HashMap<String,String>(); 

map.put(“key”,”value”); 

[<key1,value1>,<key2,value2>,<key3,value3>] 

HashMap底層實現是數組+鏈表,用來存儲<key,value>形式的數據,當我們調用put(key,value)時,首先會通過hash(key) 來獲取key的hash值,hash值對數組長度進行取模運算,定位到數組的一個存儲位置 bucket,如果bucket沒有發生沖突的話則直接放入數組,發生沖突的話則以鏈表的形式存儲,jdk1.8之后引入了紅黑樹,鏈表的長度超過8之后會使用紅黑樹,小於6之后則又轉換回來。

如下2、3條皆為第1條的補充,預防面試官細問。

2、jdk1.8中對hash算法和尋址算法的優化

jdk1.8中對hash算法進行了優化,之前在對key進行hash(key)計算時,采用的是取模運算,即第1條提到的,而優化后采用的是尋址算法,即:(n-1) & hash 『n為數組長度』

為什么要使用尋址算法呢?首先 「hash & (n-1)」 效果跟 hash 對 n 取模的效果是一樣的, 但是『&』與運算的性能要優於 hash 對 n 取模。

3、hash沖突?怎么解決?

當我們put<key,value>時,首先通過hash(key)計算得到的hash值,再通過『&』與運算之后,得到了數組存儲位置bucket,但此時有可能出現兩個不同的key卻計算出相等的bucket,舉個例子:

數組A[0]位置計算出存放<張三,我是張三>數據,而在put<李四,我是李四>數據時,也計算為存放在A[0]位置,一個位置想存放兩個數據?這就出現hash沖突了,怎么處理呢?

JDK是這樣處理的,它會在這個位置(A[0])掛一個鏈表,這個鏈表表里面存放出現沖突的數據,即:讓多個<key,value>數據同時放在數組的一個位置里。

get(key)時怎么取呢?當我們調用get(key)定位到數組位置時,如果發現這個位置掛載的是一個鏈表,那么就遍歷鏈表,從里面找到自己想要的那個<key,value>數據。

格外補充:這個地方,在JDK1.8之后引入了紅黑樹的概念,首先我們看一下為啥要引入紅黑樹,如果沒有引入紅黑樹,當數組掛載的鏈表達到一定長度之后,查詢是非常耗時的,性能比較差,時間復雜度為:O(n)「讀作:偶en」。

JDK1.8的優化就是,當鏈表的長度發到了一定長度后(8)會自動轉換為紅黑樹,遍歷一棵紅黑樹查找一個元素的時間復雜度為:O(logn)「讀作:偶,老個en」,性能相對鏈表要高一些。

簡單總結一下:

  1. 出現hash沖突的原因?兩個不同的key計算出相同的數組存放位置;

  2. 初期是怎么解決的?在出現數組沖突的位置掛一個鏈表,實現存放多個數據。

  3. JDK1.8的優化?當數組長度達到一定值后自動轉換為紅黑樹,降低時間復雜度。

4、HashMap是如何擴容的?

HashMap底層是一個數組,當數組滿了之后,他會自動進行2倍擴容,用於盛放更多的數據。

比如,本來數組默認長度=16,擴容后*2=32。

擴容后還有一步操作:rehash,重新對每個hash值進行尋址,也就是用每個hash值跟新的數組長度 n-1 進行『&』與運算操作。

補充:擴容之后的與運算可能會導致之前的發生hash沖突的元素不再發生沖突。

延伸一:之前一直在背的面試題中, 提到 HashMap 在多線程是不安全的「死循環」,為啥呢?或為什么說HashMap可能會導致死循環?
 
這個過程就是發生在擴容階段,在jdk1.7之前,hashmap在擴容到2倍新容器時,由於采用的是頭插法「頭插法就是總是把新增結點插在頭部」,會造成鏈表翻轉形成閉環,也就是形成死循環,jdk1.8之后就不再采用頭插法了,而是直接插入鏈表尾部,因此不會形成環形鏈表形成死循環,但是在多線程的情況下仍然是不安全的,在put數據時如果出現兩個線程同時操作,可能會發生數據覆蓋,引發線程不安全,總之,用ConcurrentHashMap沒錯了。 
 
延伸二:這為什么說HashTable效率低下呢?
 

HashTable使用synchronized關鍵字來保證線程安全。當一個線程訪問HashTable的同步方法,其他線程也訪問HashTable的同步方法就會進入阻塞或輪訓狀態。這個的同步方法包括讀和寫,可以理解HashTable只有一把鎖,所有的線程不管做什么,都是競爭這一把鎖,例如線程1使用put進行元素添加,線程2不但不能使用put來添加元素,也不能使用get方法來獲取元素,顯然這效率是多低。

博客地址:https://www.cnblogs.com/niceyoo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM