日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

美团面试题:Hashmap的结构,1.7和1.8有哪些区别,深入的分析

發布時間:2025/3/15 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 美团面试题: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沖突產生,那么就不會進行擴容了,減少了一次無用擴容,也減少了內存的使用

void addEntry(int hash, K key, V value, int bucketIndex) {//這里當錢數組如果大于等于12(假如)閾值的話,并且當前的數組的Entry數組還不能為空的時候就擴容if ((size >= threshold) && (null != table[bucketIndex])) {//擴容數組,比較耗時resize(2 * table.length);hash = (null != key) ? hash(key) : 0;bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);}void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];//把新加的放在原先在的前面,原先的是e,現在的是new,next指向etable[bucketIndex] = new Entry<>(hash, key, value, e);//假設現在是newsize++;}

(2)為什么在JDK1.8中進行對HashMap優化的時候,把鏈表轉化為紅黑樹的閾值是8,而不是7或者不是20呢(面試蘑菇街問過)?

如果選擇6和8(如果鏈表小于等于6樹還原轉為鏈表,大于等于8轉為樹),中間有個差值7可以有效防止鏈表和樹頻繁轉換。假設一下,如果設計成鏈表個數超過8則鏈表轉換成樹結構,鏈表個數小于8則樹結構轉換成鏈表,如果一個HashMap不停的插入、刪除元素,鏈表個數在8左右徘徊,就會頻繁的發生樹轉鏈表、鏈表轉樹,效率會很低。
還有一點重要的就是由于treenodes的大小大約是常規節點的兩倍,因此我們僅在容器包含足夠的節點以保證使用時才使用它們,當它們變得太小(由于移除或調整大小)時,它們會被轉換回普通的node節點,容器中節點分布在hash桶中的頻率遵循泊松分布,桶的長度超過8的概率非常非常小。所以作者應該是根據概率統計而選擇了8作為閥值

?//Java中解釋的原因* Because TreeNodes are about twice the size of regular nodes, we* use them only when bins contain enough nodes to warrant use* (see TREEIFY_THRESHOLD). And when they become too small (due to* removal or resizing) they are converted back to plain bins. ?In* usages with well-distributed user hashCodes, tree bins are* rarely used. ?Ideally, under random hashCodes, the frequency of* nodes in bins follows a Poisson distribution* (http://en.wikipedia.org/wiki/Poisson_distribution) with a* parameter of about 0.5 on average for the default resizing* threshold of 0.75, although with a large variance because of* resizing granularity. Ignoring variance, the expected* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /* factorial(k)). The first values are:** 0: ? ?0.60653066* 1: ? ?0.30326533* 2: ? ?0.07581633* 3: ? ?0.01263606* 4: ? ?0.00157952* 5: ? ?0.00015795* 6: ? ?0.00001316* 7: ? ?0.00000094* 8: ? ?0.00000006* more: less than 1 in ten million



(二)哈希表如何解決Hash沖突?


(三)為什么HashMap具備下述特點:鍵-值(key-value)都允許為空、線程不安全、不保證有序、存儲位置隨時間變化


(四)為什么 HashMap 中 String、Integer 這樣的包裝類適合作為 key 鍵


(五)HashMap 中的 key若 Object類型, 則需實現哪些方法?

總結

以上是生活随笔為你收集整理的美团面试题:Hashmap的结构,1.7和1.8有哪些区别,深入的分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。