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目前被使用中;
相关文章
相关标签/搜索