HashMap 分析

#Hash #HashMap

Java 中的 HashMap 是散列表的实现,有必要 review 一下一个工业级的散列表实现要考量的 Key Point:

  1. 散列表的三个基本元素:key、散列函数、value, key 通过散列函数计算得到 value 存储的位置

  2. 设计散列函数

  3. 控制散列表的装载因子,初始大小和动态扩容策略

  4. 有效解决散列冲突

  5. 对一个工业级散列表的实现要遵守几点:首先支持快速查询、删除、插入等操作;其次,内存占用要合理,不要过多浪费内存;最后,性能稳定,极端情况下,散列表的性能也不能退化到无法接受

总之,我们在结合使用场景和具体业务数据考虑设计散列表实现时,要抓住上面的 key point;接下来,从这几个点来验证一下 Java 中的 HashMap 实现

  • HashMap 的散列函数设计

散列函数的设计要点是:首先,不能复杂,太复杂的散列函数也会影响程序性能;其次,散列函数计算后得出的值要尽可能随机和均匀。

static final int hash(Object key) {
    int h;
    // 先取对象 key 的 hashCode
    // 最后返回 h ^ (h >>> 16),将hashCode的高16位移到低16位,然后做异或,保证了很好的随机性,位运算本身是很高效的
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// 在插入元素时计算key要插入的元素位置
// 通过 (n - 1) & hash 来计算位置,n 代表数组的 capacity,hash & (n - 1) 相当于 hash % n, 也就是除留余数法
if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);


// Object 的 hashCode 返回的是 int
public native int hashCode();

可见,HashMap 的散列函数设计分为两步,先对 key 求hash值,然后再根据 HashMap 的节点(本质是一个数组)容量来计算数组中的位置;求 key hash值的方式简单高效,位移后异或随机且高效,所以总体非常符合散列函数的设计要求。( 注:A % B = A & (B - 1) )

  • 控制散列表的装载因子,初始大小和动态扩容策略

HashMap 的实现底层用了数组,数组有容量范围的;装载因子表示了空槽位的多少,计算方式为:已经使用的槽位数/散列表的总长度。 从装载因子我们可以得出一些结论:当装载因子越大,散列表里的元素就越多,空闲位置就越少,散列冲突的概率就越大,会导致整个插入和查找的时间变长,最糟糕的情况会退化成链表的查找和插入时间复杂度,这是不可忍受的。这个时候就需要进行动态扩容策略来调整装载因子,让他不会太大;

但是当装载因子太小时,会导致浪费内存空间,所以保证装载因子在一个合理的值是比较重要的。

从源代码分析可见,工业级散列表的实现还是比较复杂的,但是原理和思想才是最重要的: 1. 首先分配一个初始容量,当然也可以通过构造函数来指定,固定一个装载因子,计算出触发扩容的阈值 2. 在每次插入时,判断元素数量是否到达阈值,到了就触发扩容策略 3. 扩容策略是在原来的基础上double,如果没有指定或者为0就使用默认初始容量16 4. 扩容的过程就是新申请一个更大容量(原来的2倍)的数组,将老数据copy过去,重点是位置索引的重新计算,然后是否老数组 5. HashMap 的实现使用了链表法,后面会分析到,还会涉及到红黑树的分裂,这个过程就比较复杂了

  • 有效解决散列冲突

无论设计的多么好的散列函数,都会发生散列冲突,当冲突的时候就必须解决散列冲突,常见的方法有:开放寻址法和链表法。

HashMap 采用的方式是链表法

盗用一张图,就是这样的

但是,当某个位置的链表过长时,会造成查找和插入的时间复杂度增加,HashMap 在这里做了一个优化,当链表长度超过某个值时,就将链表转换成红黑树,当红黑树的元素个数小于某个值时,会转成链表.

所以在插入、查找、删除等操作时,HashMap 可以提供比较高的效率,基本都是 O(1) 的时间复杂度。这得以于 HashMap 在各方面的优化:

  1. 对散列函数的优化,使用自定义的hash函数重新计算hash,计算hash的时候使用位操作,效率更高

  2. 对扩容策略的优化,扩容带来散列冲突概率的降低

  3. 对散列冲突的优化,当链表性能下降时,转成红黑树,红黑树加速了查找、插入的速度

  4. etc.

还可以参考:

最后更新于

这有帮助吗?