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對應的方法可能有較大差異,如果有什么地方講的不正確,歡迎指出。有需要思維導圖原圖的可以給我留言,我發給你。