美团面试题:Hashmap的结构,1.7和1.8有哪些区别,深入的分析
(一) 真實面試題之:Hashmap的結構,1.7和1.8有哪些區別
不同點:
(1)JDK1.7用的是頭插法,而JDK1.8及之后使用的都是尾插法,那么他們為什么要這樣做呢?因為JDK1.7是用單鏈表進行的縱向延伸,當采用頭插法時會容易出現逆序且環形鏈表死循環問題。但是在JDK1.8之后是因為加入了紅黑樹使用尾插法,能夠避免出現逆序且鏈表死循環的問題。
(2)擴容后數據存儲位置的計算方式也不一樣:1. 在JDK1.7的時候是直接用hash值和需要擴容的二進制數進行&(這里就是為什么擴容的時候為啥一定必須是2的多少次冪的原因所在,因為如果只有2的n次冪的情況時最后一位二進制數才一定是1,這樣能最大程度減少hash碰撞)(hash值 & length-1)
2、而在JDK1.8的時候直接用了JDK1.7的時候計算的規律,也就是擴容前的原始位置+擴容的大小值=JDK1.8的計算方式,而不再是JDK1.7的那種異或的方法。但是這種方式就相當于只需要判斷Hash值的新增參與運算的位是0還是1就直接迅速計算出了擴容后的儲存方式。
在計算hash值的時候,JDK1.7用了9次擾動處理=4次位運算+5次異或,而JDK1.8只用了2次擾動處理=1次位運算+1次異或。
擴容流程對比圖:
(3)JDK1.7的時候使用的是數組+ 單鏈表的數據結構。但是在JDK1.8及之后時,使用的是數組+鏈表+紅黑樹的數據結構(當鏈表的深度達到8的時候,也就是默認閾值,就會自動擴容把鏈表轉成紅黑樹的數據結構來把時間復雜度從O(n)變成O(logN)提高了效率)
這里在重新進行補充兩個問題:(2019-09-03)
(1)為什么在JDK1.7的時候是先進行擴容后進行插入,而在JDK1.8的時候則是先插入后進行擴容的呢?
其實就是當這個Map中實際插入的鍵值對的值的大小如果大于這個默認的閾值的時候(初始是16*0.75=12)的時候才會觸發容,
//這個是在JDK1.8中的先插入后擴容 if (++size > threshold)resize();其實這個問題也是JDK8對HashMap中,主要是因為對鏈表轉為紅黑樹進行的優化,因為你插入這個節點的時候有可能是普通鏈表節點,也有可能是紅黑樹節點,但是為什么1.8之后HashMap變為先插入后擴容的原因,我也有點不是很理解?歡迎來討論這個問題?
但是在JDK1.7中的話,是先進行擴容后進行插入的,就是當你發現你插入的桶是不是為空,如果不為空說明存在值就發生了hash沖突,那么就必須得擴容,但是如果不發生Hash沖突的話,說明當前桶是空的(后面并沒有掛有鏈表),那就等到下一次發生Hash沖突的時候在進行擴容,但是當如果以后都沒有發生hash沖突產生,那么就不會進行擴容了,減少了一次無用擴容,也減少了內存的使用
(2)為什么在JDK1.8中進行對HashMap優化的時候,把鏈表轉化為紅黑樹的閾值是8,而不是7或者不是20呢(面試蘑菇街問過)?
如果選擇6和8(如果鏈表小于等于6樹還原轉為鏈表,大于等于8轉為樹),中間有個差值7可以有效防止鏈表和樹頻繁轉換。假設一下,如果設計成鏈表個數超過8則鏈表轉換成樹結構,鏈表個數小于8則樹結構轉換成鏈表,如果一個HashMap不停的插入、刪除元素,鏈表個數在8左右徘徊,就會頻繁的發生樹轉鏈表、鏈表轉樹,效率會很低。
還有一點重要的就是由于treenodes的大小大約是常規節點的兩倍,因此我們僅在容器包含足夠的節點以保證使用時才使用它們,當它們變得太小(由于移除或調整大小)時,它們會被轉換回普通的node節點,容器中節點分布在hash桶中的頻率遵循泊松分布,桶的長度超過8的概率非常非常小。所以作者應該是根據概率統計而選擇了8作為閥值
(二)哈希表如何解決Hash沖突?
(三)為什么HashMap具備下述特點:鍵-值(key-value)都允許為空、線程不安全、不保證有序、存儲位置隨時間變化
(四)為什么 HashMap 中 String、Integer 這樣的包裝類適合作為 key 鍵
(五)HashMap 中的 key若 Object類型, 則需實現哪些方法?
總結
以上是生活随笔為你收集整理的美团面试题:Hashmap的结构,1.7和1.8有哪些区别,深入的分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zcmu-1646 盒子游戏
- 下一篇: zcmu-1932