Java集合中的Map

Map是用於保存具有映射關係的數據集合,它具有雙列存儲的特點,即一次必須添加兩個元素,即一組鍵值對<Key,Value>,其中Key的值不可重複(當Key的值重複的時候,後面插入的對象會將之前插入的具有相同的Key值的對象覆蓋掉),Value的值可重複。Map作爲接口,它最常見的實現類是HashMap和TreeMap,作爲接口它抽取了所有實現類的共有方法。同時Map不具有帶索引的方法,因此無法使用普通for循環來遍歷Map集合。

  • HashMap(數組+鏈表+紅黑樹)

HashMap 根據鍵的 hashCode 值存儲數據,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashMap 最多隻允許一條記錄的鍵爲 null,允許多條記錄的值爲 null。 HashMap 非線程安全,即任一時刻可以有多個線程同時寫 HashMap,可能會導致數據的不一致。如果需要滿足線程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有線程安全的能力,或者使用 ConcurrentHashMap。 下圖是Java7中HashMap 的結構實現:

HashMap 裏面是一個數組,然後數組中每個元素是一個單向鏈表。上圖中每個綠色的實體是嵌套類 Entry 的實例, Entry 包含四個屬性: key, value, hash 值和用於單向鏈表的 next。

  1. capacity:當前數組容量,始終保持 2^n,可以擴容,擴容後數組大小爲當前的 2 倍。

  2. loadFactor:負載因子,默認爲 0.75。

  3. threshold:擴容的閾值,等於 capacity * loadFactor。

Java8 對 HashMap 進行了一些修改, 最大的不同就是利用了紅黑樹,所以其由 數組+鏈表+紅黑樹 組成。根據 Java7 HashMap 的介紹,我們知道,查找的時候,根據 hash 值我們能夠快速定位到數組的具體下標,但是之後的話, 需要順着鏈表一個個比較下去才能找到我們需要的,時間複雜度取決於鏈表的長度,爲 O(n)。爲了降低這部分的開銷,在 Java8 中, 當鏈表中的元素超過了 8 個以後,會將鏈表轉換爲紅黑樹,在這些位置進行查找的時候可以降低時間複雜度爲 O(logN)。如下圖是Java8中HashMap 的結構實現:

  • ConcurrentHashMap
  1. Segment 段

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因爲它支持併發操作,所以要複雜一些。整個 ConcurrentHashMap 由一個個 Segment 組成, Segment 代表”部分“或”一段“的意思,所以很多地方都會將其描述爲分段鎖。注意,行文中,我很多地方用了“槽”來代表一個segment。

  1. 線程安全(Segment 繼承 ReentrantLock 加鎖)

簡單理解就是, ConcurrentHashMap 是一個 Segment 數組, Segment 通過繼承ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是線程安全的,也就實現了全局的線程安全。如下圖是Java7中ConcurrentHashMap結構實現:

concurrencyLevel:並行級別、併發數、 Segment 數,怎麼翻譯不重要,理解它。默認是 16,也就是說 ConcurrentHashMap 有 16 個 Segments,所以理論上, 這個時候,最多可以同時支持 16 個線程併發寫,只要它們的操作分別分佈在不同的 Segment 上。這個值可以在初始化的時候設置爲其他值,但是一旦初始化以後,它是不可以擴容的。再具體到每個 Segment 內部,其實每個 Segment 很像之前介紹的 HashMap,不過它要保證線程安全,所以處理起來要麻煩些。

Java8 對 ConcurrentHashMap 進行了比較大的改動,Java8 也引入了紅黑樹。如下圖:

  • HashTable(線程安全)

Hashtable 是遺留類,很多映射的常用功能與 HashMap 類似,不同的是它承自 Dictionary 類,並且是線程安全的,任一時間只有一個線程能寫 Hashtable,併發性不如 ConcurrentHashMap,因爲 ConcurrentHashMap 引入了分段鎖。Hashtable 不建議在新代碼中使用,不需要線程安全的場合可以用HashMap 替換,需要線程安全的場合可以用ConcurrentHashMap 替換。

  • TreeMap(可排序)

TreeMap 實現 SortedMap 接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用 Iterator 遍歷 TreeMap 時,得到的記錄是排過序的。如果使用排序的映射,建議使用 TreeMap。在使用 TreeMap 時,key 必須實現 Comparable 接口或者在構造 TreeMap 傳入自定義的 Comparator,否則會在運行時拋出 java.lang.ClassCastException 類型的異常。

  • LinkHashMap(記錄插入順序)

LinkedHashMap 是 HashMap 的一個子類, 保存了記錄的插入順序, 在用 Iterator 遍歷LinkedHashMap 時,先得到的記錄肯定是先插入的,也可以在構造時帶參數,按照訪問次序排序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章