HashMap以及hash衝突知識

HashMap主要是用數組來存儲數據的,咱們都知道它會對key進行哈希運算,哈系運算會有重複的哈希值,對於哈希值的衝突,HashMap採用鏈表來解決的
HashMap是線程不安全的,若是被多個線程共享的操做,將會引起不可預知的問題,據sun的說法,在擴容時,會引發鏈表的閉環,在get元素時,就會無限循環,後果是cpu100%。

java

Open addressing和Chaining(也稱外部拉鍊法)是兩種不一樣的解決hash衝突的策略。當多個不一樣的key被映射到相同的slot時,chaining方式採用鏈表保存全部的value。而Open addressing則嘗試在該slot的鄰近位置查找,直到找到對應的value或者空閒的slot, 這個過程被稱做probing。常見的probing策略有Linear probing(線性探測),Quadratic probing(二次探測)和Double hashing(雙重hash)。
算法

Chaining 數組

      在分析open addressing策略以前,首先簡單介紹一下大多數的Java 核心集合類採用的chaining策略,以便比較。 java.util.HashMap有一個Entry[]類型的成員變量table,其每一個非null元素都是一個單向鏈表的表頭。 緩存

  • put():若是存在hash衝突,那麼對應的table元素的鏈表長度會大於1。
  • get():須要對鏈表進行遍歷,在遍歷的過程當中不只要判斷key的hash值是否相等,還要判斷key是否相等或者equals。
  • 對於put()和get()操做,若是其參數key爲null,那麼HashMap會有些特別的優化。

      Chaining策略的主要缺點是須要經過Entry保存key,value以及指向鏈表下個節點的引用(Map.Entry就有四個成員變量),這意味着更多的內存使用(尤爲是當key,value自己使用的內存很小時,額外使用的內存所佔的比例就顯得比較大)。此外鏈表對CPU的高速緩存不太友好。
安全

Open Addressing 性能

Linear probing (線性探測、線性再哈希) 優化

      兩次查找位置的間隔爲一固定值,即每次查找時在原slot位置的基礎上增長一個固定值(一般爲1),例如:P = (P + 1) mod SLOT_LENGTH。其最大的優勢在於計算速度快,另外對CPU高速緩存更友好。其缺點也很是明顯: spa

      假設key1,key2,key3的hash code都相同而且key1被映射到slot(p),那麼在計算key2的映射位置時須要查找slot(p), slot(p+1),計算key3的映射位置時須要查找slot(p), slot(p+1),slot(p+2)。也就是說對於致使hash衝突的全部key,在probing過程當中會重複查找之前已經查找過的位置,這種現象被稱爲clustering(彙集)。 .net


Quadratic probing
(二次探測、非線性再哈希) 線程

      兩次查找位置的間隔線性增加,例如P(i) = (P + c1*i + c2*i*i) mod SLOT_LENGTH,其中c1和c2爲常量且c2不爲0(若是爲0,那麼降級爲Linear probing)。 Quadratic probing的各方面性能介於Linear probing和Double hashing之間。


Double hashing 
(雙重哈希
       兩次查找位置的間隔爲一固定值,可是該值經過另一個hash算法生成,例如P = (P + INCREMENT(key)) mod SLOT_LENGTH,其中INCREMENT即另一個hash算法。如下是個簡單的例子:

      H(key) = key mod 10
      INCREMENT(key) = 1 + (key mod 7)
      P(15): H(15) = 5;
      P(35): H(35) = 5, 與P(15)衝突,所以須要進行probe,位置是 (5 + INCREMENT(35)) mod 10 = 6
      P(25): H(25) = 5, 與P(15)衝突,所以須要進行probe,位置是 (5 + INCREMENT(25)) mod 10 = 0

      P(75): H(75) = 5, 與P(15)衝突,所以須要進行probe,位置是 (5 + INCREMENT(75)) mod 10 = 1

      從以上例子能夠看出,跟Linear probing相比,減小了重複查找的次數。

Load Factor

      基於open addressing的哈希表的性能對其load factor屬性值很是敏感。若是該值超過0.7 (Trove maps/sets的默認load factor是0.5),那麼性能會降低的很是明顯。因爲hash衝突致使的probing次數跟(loadFactor) / (1 - loadFactor)成正比。當loadFactor爲1時,若是哈希表中的空閒slot很是少,那麼可能會致使probing的次數很是大。

Open addressing in gnu.trove.THashMap

      GNU Trove (http://trove4j.sourceforge.net/) 是一個Java 集合類庫。在某些場景下,Trove集合類庫提供了更好的性能,並且內存使用更少。如下是Trove中跟open addressing相關的幾個特性:

  • Trove maps/sets沒有使用chaining解決hash衝突,而是使用了open addressing。
  • 跟chaining相比,open addressing對hash算法的要求更高。經過TObjectHashingStrategy 接口, Trove支持定製hash算法(例如不但願使用String或者數組的默認hash算法)。
  • Trove提供的maps/sets的capaicity屬性必定是質數,這有助於減小hash衝突。
  • 跟java.util.HashSet不一樣,Trove sets沒有使用maps,所以不須要額外分配value的引用。

      跟java.util.HashMap相比,gnu.trove.THashMap沒有Entry[] table之類的成員變量,而是分別經過Object[] _set,V[] _values直接保存key和value。在邏輯上,Object[] _set中的每一個元素都有三種狀態:

  • FREE:該slot目前空閒;
  • REMOVED:該slot以前被使用過,可是目前數據已被移除;
  • OCCUPIED:該slot目前被使用中;
相關文章
相關標籤/搜索