面试再也不怕问到HashMap(二)
詳細源碼解析查看面試再也不怕問到HashMap(一)
HashMap干貨總結
1,可以用null做為key和value
2,capacity和load factor影響性能
3,線程不安全
4,HashMap不能保證隨著時間的推移Map中的元素次序是不變的。
5,迭代時并發更改了map,則會拋出ConcurrentModificationException
6,當同一個bucket上元素擁擠(默認值為8)時,會將列表轉變成紅黑樹來提高查詢性能,縮減時(默認值為6)會恢復為鏈表。紅黑樹查找位置時折半查找效率高于鏈表,而刪除操作效率低于鏈表。
7,hash(key)核心hash算法,在長度為2的冪前提下,hash值高位下移16位,參與取模,降低碰撞,另外取模部分利用2的冪減1來做&操作提高了性能。
8,鏈表狀態下碰撞元素加入時是放入鏈表尾部,因為它認為最近放入的元素可能最容易被使用。
9,繼承Serializable接口,可是字段使用transient修飾,比如table,entrySet。原因是hashcode操作依賴jvm所處的環境因素,不同環境可能有不同的hash值,做成存儲的內容既是序列化也無法通用.所以hashmap自己實現了writeObject和readObject,這里就需要知道java在序列化和反序列化一個類時是先調用writeObject和readObject,如果沒有默認調用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。
10,樹中當兩個節點的hash一樣,會利用compareTo方法比較,如果還是相同,就使用identityHashCode進行比較。
JDK1.7和1.8的HashMap的不同點
(1)在位于同一個bucket時,JDK1.8是使用的尾插法,能夠避免出現逆序且鏈表死循環的問題。JDK1.7是單鏈表進行的縱向延伸,采用頭插法能夠提高插入的效率,但是也會容易出現逆序且環形鏈表死循環問題。
(2)擴容后數據存儲位置的計算方式也不一樣:
? ? ? ?在JDK1.7的時候是直接用hash值和擴容后容量的二進制數進行&即hash值 & length-1 。
? ? ? ?而在JDK1.8的時候是 原索引位置+擴容的大小值=新的位置。這種方式只需要判斷Hash值的新增參與運算的位是0還是1就可迅速計算出了擴容后的索引位置。
(3)JDK1.7的時候使用的是數組+ 單鏈表的數據結構。但是在JDK1.8及之后,使用的是數組+鏈表+紅黑樹的數據結構(當鏈表的深度達到8的時候,也就是默認閾值,就會自動擴容把鏈表轉成紅黑樹的數據結構來把時間復雜度從O(N)變成O(logN),提高了查找效率)。
hashMap的長度為什么一定是2的次冪?
會使得在數組中位置即索引更加均勻
? ? ? ?我們看到,上面的&運算,高位是不會對結果產生影響的(hash函數采用各種位運算可能也是為了使得低位更加散列)。我們只關注低位bit,如果低位全部為1,那么對于h低位部分來說,任何一位的變化都會對結果產生影響,也就是說,要得到index=21這個存儲位置,h的低位只有這一種組合。這也是數組長度設計為必須為2的次冪的原因。
? ? ? ?如果不是2的次冪,也就是低位不是全為1此時,要使得index=21,h的低位部分不再具有唯一性了,哈希沖突的幾率會變的更大,同時,index對應的這個bit位無論如何不會等于1了,而對應的那些數組位置也就被白白浪費了。
擴容時便于快速計算新的存儲位置(java8中采用的索引的新計算方式)
只需要看看原來的hash值新增的那個bit是1還是0就好了,是0的話索引沒變,是1的話索引變成“原索引+oldCap。
HashMap和HashTable的區別
- HashMap可以存儲null值(value)和null鍵(key),而Hashtable不行。
- HashMap是非線程安全的,而Hashtable是線程安全的,這也意味著性能也會差點。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
HashMap為什么是線程不安全的?
HashMap 在并發時可能出現的問題主要是兩方面:
- put的時候導致的多線程數據不一致
? ? ? ?比如有兩個線程A和B,首先A希望插入一個key-value對到HashMap中,首先計算記錄所要落到的 hash桶的索引坐標,然后獲取到該桶里面的鏈表頭結點,此時線程A的時間片用完了,而此時線程B被調度得以執行,和線程A一樣執行,只不過線程B成功將記錄插到了桶里面,假設線程A插入的記錄計算出來的 hash桶索引和線程B要插入的記錄計算出來的 hash桶索引是一樣的,那么當線程B成功插入之后,線程A再次被調度運行時,它依然持有過期的鏈表頭但是它對此一無所知,以至于它認為它應該這樣做,如此一來就覆蓋了線程B插入的記錄,這樣線程B插入的記錄就憑空消失了,造成了數據不一致的行為。
- resize而引起死循環
? ? ? ?這種情況發生在HashMap自動擴容時,當2個線程同時檢測到元素個數超過 數組大小 × 負載因子。此時2個線程會在put()方法中調用了resize(),兩個線程同時修改一個鏈表結構會產生一個循環鏈表(JDK1.7中,會出現resize前后元素順序倒置的情況)。接下來再想通過get()獲取某一個元素,就會出現死循環。
親自動手驗證hashMap的實驗
hashMap注釋翻譯
總結
以上是生活随笔為你收集整理的面试再也不怕问到HashMap(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试再也不怕问到HashMap(一)
- 下一篇: String字符串相等判断