hashmap和hash算法研究

  總述

  hashmap做爲java中很是重要的數據結構,對於key-value類型的存儲(緩存,臨時映射表,。。。)等不可或缺,hashmap自己是非線程安全的,對於多線程條件下須要作競爭條件處理,能夠經過Collections和ConcurrentHashmap來替代。java

  Hashmap源碼探究

  數據結構

  hashmap存儲數據主要是經過數組+鏈表實現的,經過將key的hashcode映射到數組的不一樣元素(桶,hash中的叫法),而後衝突的元素放入鏈表中。算法

  

  鏈表結構(Entry)數組

  

  採用靜態內部類緩存

  存儲操做

  

  當存入的值爲null的時候,操做會找到table[0],set key爲null的值爲新值安全

  若果不是空值,則進行hash,數據結構

  hash算法

  

  能夠看到,算法進行了二次hash,使高位也參與到計算中,防止低位不變形成的hash衝突多線程

  注:這一切的目的實際上都是爲了使value儘可能分佈到不一樣的性能

  對於任意給定的對象,只要它的 hashCode() 返回值相同,那麼程序調用 hash(int h) 方法所計算獲得的 hash 碼值老是相同的。咱們首先想到的就是把 hash 值對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,在 HashMap 中是這樣作的:調用 indexFor(int h, int length) 方法來計算該對象應該保存在 table 數組的哪一個索引處。indexFor(int h, int length) 方法的代碼以下:優化

  

  這又要牽扯到hashmap的另一個問題,關於length長度的定義spa

  在put操做的開始,有判斷table是否爲空,若是爲空則會初始化table,初始化的代碼以下:

  

private void inflateTable(int toSize) {
  // Find a power of 2 >= toSize
  int capacity = roundUpToPowerOf2(toSize);
  threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  table = new Entry[capacity];
  initHashSeedAsNeeded(capacity);
}

  容量的提高都是以2的冪的方式

  這段代碼保證初始化時 HashMap 的容量老是 2 的 n 次方,即底層數組的長度老是爲 2的 n 次方。當 length 老是 2 的 n 次方時,h& (length-1)運算等價於對 length 取模,也就是h%length,可是&比%具備更高的效率。

  這看上去很簡單,其實比較有玄機的,咱們舉個例子來講明:

  假設數組長度分別爲 15 和 16,優化後的 hash 碼分別爲 8 和 9,那麼&運算後的結果以下:

  

  讀操做:

  

  讀操做經過hash後的值,同樣是調用indexFor方法找到對應的序號,而後遍歷鏈表找到對應的value返回

  resize操做

  resize只有在hashmap中元素的大小達到臨界值的時候纔會進行,而臨界值和loadFactor 參數有關,只有數量達到loadFactor *table.length纔會從新分配table,元素也將從新映射,這是很是耗性能的操做,因此最好一開始能肯定元素的大概範圍

  HashMap 的性能參數(這段直接從參考文章引來):

  HashMap 包含以下幾個構造器:

  HashMap():構建一個初始容量爲 16,負載因子爲 0.75 的 HashMap。

  HashMap(int initialCapacity):構建一個初始容量爲 initialCapacity,負載因子

  爲 0.75 的 HashMap。

  HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負載因子建立一個 HashMap。HashMap 的基礎構造器 HashMap(int initialCapacity, float loadFactor)帶有兩個參數,它們是初始容量 initialCapacity 和加載因子 loadFactor。

  initialCapacity:HashMap 的最大容量,即爲底層數組的長度。

  loadFactor:負載因子 loadFactor 定義爲:散列表的實際元素數目(n)/ 散列表的容量(m)。負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是 O(1+a),所以若是負載因子越大,對空間的利用更充分,然然後果是查找效率的下降;若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。

  HashMap 的實現中,經過 threshold 字段來判斷 HashMap 的最大容量:

  Java 代碼

 
  threshold = (int)(capacity * loadFactor);

  結合負載因子的定義公式可知, threshold 就是在此 loadFactor 和 capacity 對應下容許的最大元素數目,超過這個數目就從新 resize,以下降實際的負載因子。默認的的負載因子0.75 是對空間和時間效率的一個平衡選擇。當容量超出此最大容量時,  resize 後的 HashMap容量是容量的兩倍:

  if (size++ >= threshold)

  resize(2 * table.length);

  快速失敗

  咱們知道 java.util.HashMap 不是線程安全的,所以若是在使用迭代器的過程當中有其餘

  線程修改了 map,那麼將拋出 ConcurrentModificationException,這就是所謂 fail-fast 策略。

  這一策略在源碼中的實現是經過 modCount 域, modCount 顧名思義就是修改次數,對HashMap 內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的 expectedModCount。

相關文章
相關標籤/搜索