java基础集合之HashMap

254 阅读3分钟

java基础集合之HashMap

1、 HashMap介绍

HashMap是开发中常用的数据结构,其以键值对的存储数据,接下来我们看下hashmapde 数据结构、底层实现原理。主要介绍下下面的几方面,底层数据结构、扩容机制、寻址算法和hash算法、在1.7下的死锁问题

2、 底层数据结构

在1.7中由数组和链表构成,hash存在概率性,不可避免的存在冲突,链表其实在hash冲突情况的下产物,在jdk1.8之前采用头插法,此时就形成了链表。在一些极端情况下,hash冲突严重,会造成链表过长,此时查找效率严重降低。所以在jdk1.8引入了红黑树,当链表过长的情况下转化为红黑树。

3、 扩容机制、寻址算法、hash算法

以put为入口理解下相关的原理 寻址算法和hash算法 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }

通过hash(hey)计算hash值 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

  Hash计算主要有两个重点,计算出hash值,然后进行右移16位;在与原来的值进行抑或,其实就是hash高16位和低16位进行抑或,可以有效的减少hash冲突。

为什么不直接用 hashCode() 而是用它的高 16 位进行异或计算新 hash 值? int 类型占 32 位,可以表示 2^32 种数(范围:-2^31 到 2^31-1),而哈希表长度一般不大,在 HashMap 中哈希表的初始化长度是 16(HashMap 中的 DEFAULT_INITIAL_CAPACITY),如果直接用 hashCode 来寻址,那么相当于只有低 4 位有效,其他高位不会有影响。这样假如几个 hashCode 分别是 2^10、2^20、2^30,那么寻址结果 index 就会一样而发生冲突,所以哈希表就不均匀分布了。 为了减少这种冲突,HashMap 中让 hashCode 的高位也参与了寻址计算(进行扰动),即把 hashCode 高 16 位与 hashCode 进行异或算出 hash,然后根据 hash 来做寻址。 扩容机制: HashMap底层主要结构是数组,而数组必须开辟连续的空间;达到一定长度后会触发扩容机制,也就是resize。 主要有两个因素 ● Capacity:HashMap当前长度。 ● LoadFactor:负载因子,默认值0.75f。 怎么理解呢,当你当前容量是16,存进第13的元素的时候,判断需要进行resize。

扩容?它是怎么扩容的呢?

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。 为甚需要重新hash一遍,因为此时数组长度不一样,需要计算位置。

负载因子为什么默认值0.75f。

如果太大,hash冲突的可能性随之提高;太小有点浪费空间,这是在经验中取得一个平衡值。

红黑树

当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间,8是经过开发和实际经验来采用的。

头插是JDK1.7的那1.8的尾插是怎么样的呢?

 使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
 

就是说原本是A->B,在扩容后那个链表还是A->B

Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。

Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。