HashMap能夠說是java中最多見也是最重要的key-value存儲結構類,不少程序員可能常常用,可是不必定清楚這個類背後的數據結構和相關操做原理,爲了複習HashMap相關的知識,今天花了一天的時間整理了下有關該類的相關知識,我的認爲基本上涵蓋了HashMap相關的知識點,但願對你們有所幫助。java
博客園的編輯功能很差用,截成圖後變形嚴重,所以在這裏放個連接,(點我看大圖)爲了讓你們看得更清楚,把圖片的內容也放在這裏。程序員
一、基本概念算法
size:key-value鍵值對的數量數組
capacity:Entry數組的大小,默認16安全
loadfactor:負載因子,默認0.75,基於性能和空間的tradeoff,當太大時,會致使衝突變多,影響查詢性能,過小時,會佔用較多空間,致使浪費數據結構
threshold:極限容量,數組擴容時的臨界值,capacity*loadFactor>threshold時,會自動擴容jvm
Entry:map中數組存儲的對象,遍歷Map時就是基於Entry進行遍歷的函數
Bucket:桶,map中一個Entry位置下對應的一個鏈表,相似於一個坑,相同hashcode的鍵值對以鏈表的形式存在一個bucket裏性能
modcount:修改計數,當HashMap中的元素個數發生改變時,該值就會+1,若是該值和expectedModCount不一致,會觸發fast-fail機制,拋出ConcurrentModifycationException異常線程
二、數據結構
jdk7以前(含jdk7):數組+鏈表
jdk8開始:數組+鏈表|紅黑樹(當鏈表中元素超過8個時,會由鏈表自動轉爲紅黑樹)
三、重要特性
一、非線程安全,對應HashTable是線程安全的,由於加了synchronized關鍵字
二、無序,由於hash函數會打亂順序,而且resize後不保證在新數組中的位置和原數組中位置一致,可是會在原來位置+2^n位置
三、容許key和value均爲null,當key爲null時,存在entry[0]所在的鏈表裏
四、重要方法
equals方法: Object類定義的方法,具體介紹參考面hash方法。若是兩個對象根據equals()方法相等,則hashcode必定相等,反過來若是hashcode相同,則equals不必定相等。重寫equals方法須要知足5個特性
一、自反性:x.equals(x)永遠返回true
二、對稱性:若是x.equals(y)爲true,則y.equals(x)也爲true
三、傳遞性:若是x.equals(y)爲true,y.equals(z)爲true,則x.equals(z)也爲true
四、冪等性:若是對象沒被改變,那麼無論調用多少次,x.equal(y)的結果永遠相同
五、非空性:若是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操做:
一、先根據hashCode方法計算該對象的key的hashCode
二、經過indexFor方法計算key的hashCode在entry數組中的下標位置
三、若是該下標處爲null,則把該對象存入該節點
四、若是該下標處不爲null,則遍歷該鏈表,根據equals方法尋找是否有對應的key,若是有,則替換舊的值,不然將該對象添加到鏈表的頭部
addEntry方法:添加Entry對象的方法,添加以前會先判斷是否須要resize擴容,擴容的條件有2個:鍵值對數量>=極限容量,而且存放該對象的buckey的值非空(也就是有衝突了),假若有極限容量是12,map中有13個鍵值對,可是這13個鍵值對都存在table 數組的13個bucket裏,那麼也不會擴容的
resize方法:當達到擴容條件時調用的方法
一、當極限容量已經達到最大值2^32-1時,再也不擴容
二、若是未達到,則首先建立一個新的數組(容量爲原來數組的2倍)
若是初始化Map容量的時候不是2的n此方,會生效嗎?
不會的,由於hash會找一個比該值大的最小2的n這次方的值,好比指定了初始容量爲12,則默認的數組大小爲16
爲何是2倍?2個緣由:
一、位移運算速度快。
二、每次將對象轉移到新的數組時(即調用indexFor函數時),因爲採用的是hash&(length-1),既能減小衝突,又能保證速度,因此每次擴容都是原來的2倍。
三、遍歷map,計算原來的數組中每一個鍵值對在新數組中的index,再將其存到新數組中相應位置
remove方法:與put操做基本相反,將對象從Map中移除,需注意fast-fail問題,對應的解決辦法是:採用迭代器自己的remove方法,而不要採用hashMap的remove方法
五、和其餘集合類的區別
HashTable:
一、HashTable線程安全,由於put和remove方法里加了synchronized關鍵字
二、HashTable中的key和value都不能爲null,hashMap能夠
三、性能上,因爲HashMap非線程安全,所以速度更高
四、HashMap繼承自AbstractMap,HashTable繼承自Dictionary
五、其餘各方面區別不大,包括數據結構,存取方法等
TreeMap:一、HashMap無序,TreeMap基於紅黑樹實現,是有序的
總的來講,hashMap是一個設計很是巧妙的類,光看源碼有必定的複雜度,尤爲是不一樣版本的jdk對應的方法可能有較大差別,若是有什麼地方講的不正確,歡迎指出。有須要思惟導圖原圖的能夠給我留言,我發給你。