Map接口概述
Map與Collection並列存在。用於存儲具有映射關系的數據 : key-value
- Map 中的 key 和 value 都可以是任何引用類型的數據
- Map 中的 key 用Set來存放,不允許重復,key所在的類須重寫hashCode()和equals()方法
- 常用String類作為Map的key
- key 和 value 之間存在單向一對一關系,即通過指定的 key 總能找到唯一的、確定的 value
- Map接口的常用實現類:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用頻率最高的實現類
常用實現類
-
HashMap
: 作為Map的主要實現類;線程不安全的,效率高,key可以為nullLinkedHashMap
:保證在遍歷map元素時,可以照添加的順序實現遍歷。
原理:在HashMap底層結構基礎上,添加了一對指針,指向 前一個和后一個元素。對於頻繁的遍歷操作,此類執行效率高於HashMap
-
TreeMap
:對添加的key-value對進行排序,實現排序遍歷。此時考慮key的自然排序或定制排序。底層使用紅黑樹 -
Hashtable
:作為古老的實現類;線程安全的,效率低;key和value都不能為null
底層實現原理(重要)
HashMap在jdk7中實現原理
HashMap map = new HashMap():
在實例化以后,底層創建了長度是16的一維數組Entry[] table
首先,調用key1所在類的hashCode()計算key1哈希值,此哈希值經過某種算法計算以后,得到在 Entry數組中的存放位置。
如果此位置上的數據為空,此時的key1-value1添加成功。----情況1
如果此位置上的數據不為空,意味着此位置上存在一個或多個數據(以鏈表形式存在),比較key1和已經存在的一個或多個數據的哈希值
如果key1的哈希值與已經存在的數據的哈希值都不相同,此時key1-value1添加成功。----情況2
如果key1的哈希值和已經存在的某一個數據(key2-value2)的哈希值相同,繼續比較:調用key1所在類的equals(key2)方法,如果equals()返回false:此時key1-value1添加成功。----情況3
如果equals()返回true,使用value1替換value2,put()中存在替換的功能。
補充:關於情況2和情況3:此時key1-value1和原來的數據以鏈表的方式存儲。
在不斷的添加過程中,會涉及到擴容問題,當超出臨界值(且要存放的位置非空)時,則會擴容。
默認的擴容方式:擴容為原來容量的2倍,並將原的數據復制過來。
HashMap在jdk8中的原理
- new HashMap():初始化時底層沒創建一個長度為16的數組
- 首次調用put()方法時,底層創建長度為16的數組
- jdk 8底層的數組是:Node[],而非Entry[]
- jdk7底層結構:數組+鏈表。jdk8中底層結構:數組+鏈表+紅黑樹。
形成鏈表時,七上八下(jdk7:新的元素指向舊的元素。jdk8:舊的元素指向新的元素)
HashMap底層典型屬性的屬性的說明:
- DEFAULT_INITIAL_CAPACITY : HashMap的默認容量,16
- MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
- DEFAULT_LOAD_FACTOR****:HashMap的默認加載因子
- TREEIFY_THRESHOLD****:Bucket中鏈表長度大於該默認值,轉化為紅黑樹
- UNTREEIFY_THRESHOLD****:Bucket中紅黑樹存儲的Node小於該默認值,轉化為鏈表
- MIN_TREEIFY_CAPACITY:桶中的Node被樹化時最小的hash表容量。(當桶中Node的數量大到需要變紅黑樹時,若hash表容量小於MIN_TREEIFY_CAPACITY時,此時應執行
- resize:擴容操作這個MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。
- table:存儲元素的數組,總是2的n次冪
- entrySet:存儲具體元素的集
- size:HashMap中存儲的鍵值對的數量
- modCount:HashMap擴容和結構改變的次數。
- **threshold****:擴容的臨界值,
=容量*填充因子
- loadFactor:填充因子
存儲結構
HashMap的內部存儲結構其實是數組+鏈表+樹的結合
。當實例化一個HashMap時,會初始化initialCapacity和loadFactor,在put第一對映射關系時,系統會創建一個長度為initialCapacity的Node數組,這個長度在哈希表中被稱為容量(Capacity),在這個數組中可以存放元素的位置我們稱之為“桶”(bucket),每個bucket都有自己的索引,系統可以根據索引快速的查找bucket中的元素。
每個bucket中存儲一個元素,即一個Node對象,但每一個Node對象可以帶一個引用變量next,用於指向下一個元素,因此,在一個桶中,就有可能生成一個Node鏈。也可能是一個一個TreeNode對象,每一個TreeNode對象可以有兩個葉子結點left和right,因此,在一個桶中,就有可能生成一個TreeNode樹。而新添加的元素作為鏈表的last,或樹的葉子結點。
那么HashMap什么時候進行擴容和樹形化呢?
當HashMap中的元素個數超過數組大小(數組總大小length,不是數組中個數size)loadFactor 時 , 就會進行數組擴容 , loadFactor 的默認 值 (DEFAULT_LOAD_FACTOR)為0.75,這是一個折中的取值。也就是說,默認情況下,數組大小(DEFAULT_INITIAL_CAPACITY)為16,那么當HashMap中元素個數超過16*0.75=12
(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴展為 2*16=32
,即擴大一倍,然后重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以如果我們已經預知HashMap中元素的個數,那么預設元素的個數能夠有效的提高HashMap的性能。 當HashMap中的其中一個鏈的對象個數如果達到了8個,此時如果capacity沒有達到64,那么HashMap會先擴容解決,如果已經達到了64,那么這個鏈會變成樹,結點類型由Node變成TreeNode類型。當然,如果當映射關系被移除后,下次resize方法時判斷樹的結點個數低於6個,也會把樹再轉為鏈表。
常見問題
是否可為null
- HashMap允許一個鍵為null的鍵值對存在,允許多個值為null的鍵值對存在
- TreeMap不允許鍵為null