一張思維導圖帶你梳理HashMap相關知識


         HashMap可以說是java中最常見也是最重要的key-value存儲結構類,很多程序員可能經常用,但是不一定清楚這個類背后的數據結構和相關操作原理,為了復習HashMap相關的知識,今天花了一天的時間整理了下有關該類的相關知識,個人認為基本上涵蓋了HashMap相關的知識點,希望對大家有所幫助。

        博客園的編輯功能不好用,截成圖后變形嚴重,因此在這里放個鏈接,(點我看大圖)為了讓大家看得更清楚,把圖片的內容也放在這里。

        1、基本概念

               size:key-value鍵值對的數量

capacity:Entry數組的大小,默認16

loadfactor:負載因子,默認0.75,基於性能和空間的tradeoff,當太大時,會導致沖突變多,影響查詢性能,太小時,會占用較多空間,導致浪費

threshold:極限容量,數組擴容時的臨界值,capacity*loadFactor>threshold時,會自動擴容

Entry:map中數組存儲的對象,遍歷Map時就是基於Entry進行遍歷的

Bucket:桶,map中一個Entry位置下對應的一個鏈表,類似於一個坑,相同hashcode的鍵值對以鏈表的形式存在一個bucket里

modcount:修改計數,當HashMap中的元素個數發生改變時,該值就會+1,如果該值和expectedModCount不一致,會觸發fast-fail機制,拋出ConcurrentModifycationException異常

2、數據結構

        jdk7之前(含jdk7):數組+鏈表

jdk8開始:數組+鏈表|紅黑樹(當鏈表中元素超過8個時,會由鏈表自動轉為紅黑樹)

3、重要特性

1、非線程安全,對應HashTable是線程安全的,因為加了synchronized關鍵字

2、無序,因為hash函數會打亂順序,並且resize后不保證在新數組中的位置和原數組中位置一致,但是會在原來位置+2^n位置

3、允許key和value均為null,當key為null時,存在entry[0]所在的鏈表里

4、重要方法

equals方法: Object類定義的方法,具體介紹參考面hash方法。如果兩個對象根據equals()方法相等,則hashcode一定相等,反過來如果hashcode相同,則equals不一定相等。重寫equals方法需要滿足5個特性

1、自反性:x.equals(x)永遠返回true

2、對稱性:如果x.equals(y)為true,則y.equals(x)也為true

3、傳遞性:如果x.equals(y)為true,y.equals(z)為true,則x.equals(z)也為true

4、冪等性:如果對象沒被改變,那么不管調用多少次,x.equal(y)的結果永遠相同

5、非空性:如果x非空,則x.equals(null) 永遠為false

hash方法:Object類定義的方法,用於計算key的hashCode,默認是一個對象在JVM內存地址的哈希值。當需要比較對象的值而不是對象本身時,通常需要重寫hash方法和equals方法,因為默認的equals方法比較的是對象在jvm內存地址的值,如果只重寫hash         方法,那么equals方法比較的其實是2個對象的堆地址;反過來,如果只重寫equals方法,那么相同對象的hashcode可能不一致,也會導致比較結果不正確

indexFor方法:計算hashCode在entry數組中的位置,用了個很巧妙的算法h = h&(table.length -1 ),后面會解釋為什么每次resize的時候,都是原來的2倍

put方法:put方法先判斷key是否為null,如果為null,則調用putForNullKey方法(jdk8之前),否則按下面思路執行put操作:

                1、先根據hashCode方法計算該對象的key的hashCode

2、通過indexFor方法計算key的hashCode在entry數組中的下標位置

3、如果該下標處為null,則把該對象存入該節點

4、如果該下標處不為null,則遍歷該鏈表,根據equals方法尋找是否有對應的key,如果有,則替換舊的值,否則將該對象添加到鏈表的頭部

addEntry方法:添加Entry對象的方法,添加之前會先判斷是否需要resize擴容,擴容的條件有2個:鍵值對數量>=極限容量,並且存放該對象的buckey的值非空(也就是有沖突了),假如有極限容量是12,map中有13個鍵值對,但是這13個鍵值對都存在table          數組的13個bucket里,那么也不會擴容的

       resize方法:當達到擴容條件時調用的方法

 1、當極限容量已經達到最大值2^32-1時,不再擴容

2、如果未達到,則首先創建一個新的數組(容量為原來數組的2倍)

如果初始化Map容量的時候不是2的n此方,會生效嗎?
不會的,因為hash會找一個比該值大的最小2的n此次方的值,比如指定了初始容量為12,則默認的數組大小為16

為什么是2倍?2個原因:
1、位移運算速度快。
2、每次將對象轉移到新的數組時(即調用indexFor函數時),由於采用的是hash&(length-1),既能減少沖突,又能保證速度,所以每次擴容都是原來的2倍。

3、遍歷map,計算原來的數組中每個鍵值對在新數組中的index,再將其存到新數組中相應位置

        remove方法:與put操作基本相反,將對象從Map中移除,需注意fast-fail問題,對應的解決辦法是:采用迭代器本身的remove方法,而不要采用hashMap的remove方法 

5、和其他集合類的區別

       HashTable:

              1、HashTable線程安全,因為put和remove方法里加了synchronized關鍵字

2、HashTable中的key和value都不能為null,hashMap可以

3、性能上,由於HashMap非線程安全,因此速度更高

4、HashMap繼承自AbstractMap,HashTable繼承自Dictionary

5、其他各方面區別不大,包括數據結構,存取方法等

       TreeMap:1、HashMap無序,TreeMap基於紅黑樹實現,是有序的

        總的來說,hashMap是一個設計非常巧妙的類,光看源碼有一定的復雜度,尤其是不同版本的jdk對應的方法可能有較大差異,如果有什么地方講的不正確,歡迎指出。有需要思維導圖原圖的可以給我留言,我發給你。

 


免責聲明!

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



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