理解HashMap(jdk8)

HashMap 數據結構

 圖中的 "table" 在 HashMap 中是一個 Node<K,V> 數組 。HashMap 內部數據結構是由數組鏈表樹形結構組合而成的。java

 

什麼是hash?

 百度百科:hash 通常被翻譯爲 「散列」, 也有直接音譯爲「哈希」的,就是把任意長度的輸入,經過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間一般遠小於輸入的空間,不一樣的輸入可能會散列成相同的輸出,因此不可能從散列值來肯定惟一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。算法

wikipedia : 數組

哈希函數 : 哈希函數就是能將任意長度的數據映射爲固定長度的數據的函數。哈希函數返回的值被叫作哈希值、哈希碼、散列,或者直接叫作哈希。一個使用場景就是哈希表,哈希表被普遍用於快速搜索數據。安全

哈希表:哈希表是一種能實現關聯數組的抽象數據結構,能把不少「鍵」映射到不少「值」上。哈希表使用哈希函數來計算索引,一個索引對應一個值。數據結構

 

HashMap 初始化

// initialCapacity 初始化容量大小
// loadFactor 負載因子
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        // threshold 是HashMap是否要進行擴容的標記量
        this.threshold = tableSizeFor(initialCapacity);
    }

initialCapacity , loadFactor  這兩個參數都影響着HashMap的性能。initialCapacity 決定了下一次 resize 後的容量(capacity << 1) ,  loadFactor  決定了 resize 發生的條件 (size > (capacity * loadFactor )) (通常狀況下 , 極端狀況下是 size > Integer.MAX_VALUE) 。若是初始化時不指定這兩個參數,會使用默認值 , capacity  = 16 , loadFactor  = 0.75 。對於 16 的容量空間,若是不能充分利用的話會形成空間資源的浪費。(我的認爲高手之因此高都體如今細節之中)多線程

 

散列過程

散列的過程就是將存入的元素均勻的分佈到HashMap內部Node數據的過程。均勻分佈指的是 , 數組中的每一個位置儘可能都存儲了一個Node節點,而且該位置上的鏈表只有一個元素。散列分佈的越均勻進行碰撞檢測的次數就越少,查詢性能就越高。併發

這就是一個散列較爲均勻的 , 查詢時最好狀況下能夠直接定位 , 最壞狀況下須要進行一次碰撞檢測。函數

 

這是一個散列的不均勻的,查詢時會進行屢次碰撞檢測形成效率較低。性能

 

碰撞檢測

((capacity - 1) & hash) 會計算出 key 存儲在 Node 數組中的那個位置上 (獲得的值始終會落在 Node數組的長度範圍內 , 等同於 hash % capacity  , 不過位運算的效率更高), 若是發現該位置上已經存在Node 了,會將新存入的數據做爲鏈表的尾節點。這樣存儲和查詢時都會進行碰撞檢測。碰撞檢測其實就是比較傳入的 key 的 hash 與同一 bucket 上全部的 key 的 hash 是否一致的過程。 jdk8 在這方面作了優化,加入了樹型結構來彌補鏈表線性結構性能較低的不足。優化

提升碰撞檢測的性能 , 從代碼中也能看出來  , 該運算的最理想狀況(hash 相等狀況下)是執行兩步 , 比較 hash , 比較 key 。 最壞狀況是執行 4 步 , 只要取最好狀況就達到了提升性能的目的 。以此類推 key 就能夠用一些 String , Enum 這之類的數據類型。

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) {
  // .......
}

 

reszie

rezise 是一個較爲消耗性能的過程 , 在首次向HashMap中存入元素的時候會進行首次resize ,  在以後每當產生新節點(這裏的節點指的是Node) , 同時 size > threshold 的時候會進行 resize ,resize 的過程也是 rehash 的過程。本篇儘可能不分析源碼只是作總體的,概念上的瞭解。

 

併發安全

HashMap 是不支持併發的 , 在併發修改時它採用 fail-fast 的策略 , 拋出 ConcurrentModificationException 。 多線程環境下操做HashMap有可能會形成死循環 , 在 resize 的過程中。不要在多線程場景下使用HashMap 。

相關文章
相關標籤/搜索