首页 > 技术文章 > Hash

asfeixue 2017-10-23 18:06 原文

 

HashMap

HashMap是一个采用哈希表的键值对集合,这里从如下几个问题解读HashMap。

  1. HashMap工作原理?

  2. 如何做到fail-fast?可靠吗?

  3. 容量超过负载因子定义的容量,怎么处理?

  4. capacity & load factor 设置参考因素?

  5. HashMap为何线程不安全?

  6. 为什么容量必须为2的整数次幂?

HashMap工作原理

HashMap 是一个采用哈希表实现的键值对集合。put元素时,通过hash计算出下标,然后存储在合适位置的bucket中。get元素时,通过hash计算出下标,然后遍历桶中的链表获取元素。存储基于一个数组,然后数组的每一个元素都是链表。

如何做到fail-fast?可靠吗?

HashMap内部有一个modCount属性,累计每一次结构变更操作。当生成迭代器时,会记录当前的modCount值,此后每次迭代都会拿记录的值与当前值比较,有差异,则抛出ConcurrentModificationException。但是,这个属性没有volatile标记,也没有同步语义等。并发操作下不存在happens-before关系,故不一定可靠。

容量超过负载因子定义的容量,怎么处理?

会触发resize,对所有的元素做rehash,重新分布元素,并按照当前总容量的2倍扩容。扩容后,阈值等也会随之调整。

capacity & load factor设置参考因素?

hashmap基于hashing的原理,本质上是用空间换时间来提升搜索效率,这2个参数的设置参考因素也是基于这一点来进行。

1.capacity代表容量,过小容易导致过多的resize动作;过高容易浪费大量的存储空间以及提高检索时间。

2.load factor表示负载比例,比例过低,容量浪费大量的空间;比例过高,产生碰撞的几率更大,更多的碰撞会降低搜索的效率。

HashMap为何线程不安全?

  1. 并发做节点增删时,一个线程新增的节点,挂在另一个线程的删除节点之后,可能导致数据丢失;
  2. 并发做resize时,可能导致节点死循环;

为什么容量必须为2的整数次幂?

hash值找到对应索引的方法如下:

/**

 * Returns index for hash code h.

 */

static int indexFor(int h, int length) {

    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";

    return h & (length-1);

}

 

首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;

其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。

因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。

 

  • HashTable

  1. 为何线程安全?
  2. 获取index相对HashMap如何?

为何线程安全?

HasTable相对HashMap,基本上对所有的公开方法都增加了synchronized关键字,相对HashMap多了线程安全特性。

获取index相对HashMap如何?

相比HashMap的获取index逻辑,性能要差一些。HashMap只是一个取模动作,HashTable是取模后再取余。

  • ConcurrentHashMap

ConcurrentHashMap默认并发级别是16,通过Segment来分块管理entry集合。每个Segment都持有一个重入锁,限制并发编辑操作。

相比HashTable并发时锁整个Map,ConcurrentHashMap是锁Segment,锁粒度变小,整体性能提升。

同时对迭代器友好,在并发迭代时,不会抛出ConcurrentModificationException异常。

  • TreeMap

基于红黑树实现。深入解读可参考:http://blog.csdn.net/chenssy/article/details/26668941

  • LinkedHashMap

LinkedHashMap继承HashMap,在HashMap的功能上叠加了有序性(插入顺序,访问顺序),元素链表支持双向等功能。

LinkedHashMap扩展了HashMap中的Entry,在其中增加了Entry<K, V> before, after属性。通过这2个属性,支持双向链表。同时独立于table数组外有一个Entry<K, V> header节点,通过这个来遍历整个双向链表。

有序性则通过accessOrder属性来控制。

  • 一致性哈希

 将桶数量设置为一个极大值,机器与数据视为节点都可以被映射到这一个近似无穷大的空间中。每个机器节点可以管理顺序条件下的一组空间,数据节点hash后落在这组空间内的,会交由机器节点管理。当机器增删时,会产生管理空间的分割与合并处理,顺带归属这些空间的数据节点会做迁移。这样可以做到有限规模的数据迁移。在增删节点时,因为hash计算的缘故,机器节点分摊的数据节点可能不平衡。为了尽量做到平衡性,引入虚拟节点。每一个真实机器节点会映射几个虚拟节点。出于维护平衡性,虚拟节点会尽量分摊数据节点,做到尽量均匀。

详细可参考:

http://blog.csdn.net/cywosp/article/details/23397179

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

推荐阅读