Update HashMap.md

master
Omooo 5 years ago
parent 041237ac39
commit eb62b075b7
  1. 25
      blogs/Algorithm/HashMap.md

@ -17,7 +17,9 @@ HashMap
#### 概述
HashMap 是数组+链表+红黑树。它根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因为具有很快的访问速度,但遍历顺序是不确定的。HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,如果需要满足线程安全,可以用 Collections.synchronizedMap 方法使得 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
HashMap 底层数据结构是数组 + 链表 + 红黑树。数组的主要作用是方便快速查找,时间复杂度是 O(1),默认大小是 16,数组的下标索引是通过 key 的 hashcode 计算出来的,数组元素叫做 Node,当多个 key 的 hashcode 一致,但 key 值不相同时,单个 Node 就会转化为链表,链表的查询复杂度是 O(n),当链表的长度大于等于 8 并且数组的大小超过 64 时,链表就会转化为红黑树,红黑树的查询复杂度是 O(log(n)),简单来说,最坏的查询次数相当于红黑树的最大深度。
HashMap 非线程安全,如果需要满足线程安全,可以用 Collections.synchronizedMap 方法使得 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
#### 内部实现
@ -39,7 +41,26 @@ public class HashMap<K,V> extends AbstractMap<K,V>
在 HashMap 中,哈希桶数组的长度大小必须是 2 的 n 次方,这是一种非常规的设计,常规来说是把桶的大小设计为素数。相对来说,素数导致冲突的概率要小于非素数。HashTable 初始化桶的大小为 11,就是把桶大小设计为素数的应用。HashMap 采用这种非常规的设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap 定位哈希桶索引位置时,也加入了高位参与运算的过程。
这里存在一个问题,即使负载因子和 Hash 算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响 HashMap 的性能。于是,在 JDK 1.8 版本中,对数据结构作了进一步优化,当链表长度超过 8 时,链表就转换为红黑树(二叉查找树),利用红黑树快速增删改差的特点提高 HashMap 的性能,从 O(n) 到 O(log n)。
这里存在一个问题,即使负载因子和 Hash 算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响 HashMap 的性能。于是,在 JDK 1.8 版本中,对数据结构作了进一步优化,当链表长度大于等于 8 **并且数组长度大于等于 64 时**,链表就转换为红黑树(二叉查找树),利用红黑树快速增删改差的特点提高 HashMap 的性能,从 O(n) 到 O(log n)。如果数组长度小于 64,则只会扩容不会树化。
为什么是 8 呢?这个答案在源码中注释又说到。大概意思就是,在链表数据不多的时候,使用链表进行遍历也比较快,只有当链表数据比较多时,才会转化成红黑树,但红黑树的占用空间是链表的 2 倍,考虑到转化时间和空间消耗,所以我们需要定义出转化的边界值。
在考虑设计 8 这个值的时候,我们参考了泊松分布概率函数,由泊松分布得出结论如下:
```
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million
```
也就是说,当链表长度是 8 的时候,出现的概率是 0.000000006,不到千万分之一,所以说,正常情况下,链表的长度不可能到达 8,而一旦到达了 8,肯定是 hash 算法出了问题。
##### 确定哈希桶数组索引位置

Loading…
Cancel
Save