hashmap扩容 面试_HashMap面试,看完这一篇就够了(上)
以下HashMap源碼的解析都是基于java8來講解的。
HashMap的結構是數組加鏈表的形式(jdk7中也是),在java8中引入了紅黑樹,由于紅黑樹的時間復雜度是O(log n),引入紅黑樹是為了解決在哈希沖突很嚴重的時候導致鏈表太長,從而引起的查找效率太低的問題。
常量
※ 為什么鏈表長度大于8才進行樹化
源碼中有下面一段解釋
大體意思就是:由于TreeNode所占空間是普通Node的兩倍,所以只有在bin包含足夠多的node的時候才會轉化為Tree(有TREEIFY_THRESHOLD決定)。當bin變的很小的時候,又會轉為鏈表。當hashCode分布良好的時候,幾乎用不到tree,hash沖突很小。但是在隨機hashCode時,離散性差,會導致hash沖突嚴重,所以導致鏈表很長,這時候就要轉化為紅黑樹來提高查詢效率。按上面java官方給出的概率,鏈表長度達到8的概率是0.00000006,是很低很低的概率了,所以java也是通過大概率統計得出大于8的時候才轉化為紅黑樹。
內部類 Node
從java8開始,HashMap的節點改為了使用Node(java7使用Entry),其實都差不多,內部結構都類似。
hash hash值key key值value value值next 下一個節點方法
put方法
put方法調用了下面的putVal方法,這里和java7版本的不同有兩處:
當鏈表長度大于8的時候,會轉化為紅黑樹;而且如果Node是TreeNode類型,則按照紅黑樹的方式進行putput采用的是尾插法,這樣在擴容的時候避免了多線程情況下出現死循環(java7采用頭插法,會存在擴容時出現死循環)
get方法
get方法調用了getNode方法
HashMap擴容
HashMap的擴容存在的問題也是面試中經常問到的,首先來看下擴容的源碼
在這段代碼中,java8進行了優化的,一部分是元素位置不變的,一部分是元素位置+old capacity。它是這樣實現的:
e.hash & oldCap
這段代碼不是為了判斷元素所在的位置,而是判斷hash值在old capacity的那一位是不是0,舉個例子:hashCode是20,old capacity是16,new capacity是32,計算元素位置的方式:(n - 1) & hash
擴容前后對比發現,差距就在old capacity二進制1的位置那,所以e.hash & oldCap可以判斷出那個位置是否為1。如果為1,那么元素的新位置就是old capacity+擴容前的元素位置 即為擴容后的位置;如果為0,則擴容后的位置與擴容前相同。java8之所以這樣優化,首先是因為HashMap的capacity始終是2的冪次方,這樣就保證了e.hash & oldCap的正確性;其次這樣優化去掉了擴容時的重新hash運算,提高了效率。
擴容存在的問題
數據覆蓋看resize源碼可以發現,當兩個線程同時走到#1的位置時,如果線程1和線程2的key的hash值相同,那線程1賦值后讓出cpu,此時線程2獲得時間片,同樣也進行了賦值操作,那么線程1賦的值就被線程2給覆蓋掉了。死循環在java8中已經不存在死循環了。但是在java7中是存在的。有與java7是頭插法,所以在resize的時候,鏈表的順序會反轉,此時兩個線程同時進行resize,就有可能形成鏈式的圓圈,造成死循環。HashMap線程不安全,那該用什么
HashMap的線程版本有HashTable和Collections.synchronizedMap(map),但是這兩個都是直接加synchronized實現的,效率很低。而HashMap的線程安全版本常用的是ConcurrentHashMap,這個也是面試中經常問到的。明天會更新ConcurrentHashMap的相關面試點,歡迎大家繼續關注。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的hashmap扩容 面试_HashMap面试,看完这一篇就够了(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 超凡先锋如何倒卖超凡先锋如何倒卖装备
- 下一篇: r语言用行名称提取数据框信息显示na_学