集合之Map接口


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可以為null

    • LinkedHashMap:保證在遍歷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中的原理

  1. new HashMap():初始化時底層沒創建一個長度為16的數組
  2. 首次調用put()方法時,底層創建長度為16的數組
  3. jdk 8底層的數組是:Node[],而非Entry[]
  4. 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


免責聲明!

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



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