关于hashmap:闲聊-聊一聊hash

55次阅读

共计 901 个字符,预计需要花费 3 分钟才能阅读完成。

咱们相熟的 hash 构造,首先是一个数组元素的哈希桶,比方下图,是长度为 4 的哈希桶。

也就是说,当 key 通过 hash 计算后,对 4 进行取模,依据后果寄存这个指定地位。比方取模后值为 0,那就放第一个地位。

哈希桶的每个地位,保留的是 entry 的对象,这个 entry 对象包含 key、value 以及 entry 对象。

这个速度是十分快的,工夫复杂度是 O(1),所以 map 的性能还是不错的。

如果模数相等呢?咱们的 entry 对象中包含了 entry 对象,也就是说会通过链表,把雷同模数的 entry 连接起来。

然而数据量越来越大的时候,就会发现一个问题,那就是雷同的模数会越来越多,也就是说哈希表的抵触问题越来越重大,重大到影响到性能。

那怎么办呢?

罕用的办法是通过 rehash 扩容来解决 hash 抵触,哈希桶数量的数量,让 entry 寄存于更多的哈希桶钟。也就是下面的 4 变成 8,变成 16,这样能够间接升高抵触的概率。

看起来很完满,除了并发可能带来的死循环(参考 HashMap 源码解析),仍然有一个潜在的危险点。

失常 rehash 的时候,步骤是这样的:

  1. 创立一个新的哈希桶,数量是之前的 2 倍。
  2. 把旧的数据从新计算存入新的哈希桶中。
  3. 删除旧的哈希桶。

如果数据量很大的话,那整个过程就始终梗塞在第二个步骤中。

redis

redis 也用了哈希桶,他是这么解决的,把每一次的拷贝,都摊派在每一个申请中。

假如原长度为 4,新长度为 8。

那第一次解决申请的时候,就只解决 1 下面的 entry 链表。

此时 4 下面的 entry 链表还没解决,当第二次申请的时候,就会解决。

通过分批解决,奇妙的把一次十分耗时的操作,摊派到一个个小的操作里。(然而申请 的时候,还是要思考两个哈希桶存在的状况)。

JVM

分批操作,在 JVM 的 G1 收集器也有相似的手法。

redis 是单线程,所以须要防止耗时的 rehash 的操作导致线程阻塞,而 JVM 回收垃圾的时候,会 stop the world,也会阻塞利用,所以也要防止长时间的垃圾回收操作。

所以 G1 是会依据进展工夫来进行垃圾回收,有时候回收的垃圾,并不能开释现实的内存空间,那就会分屡次进行回收,最多 8 次,尽可能在缩小进展工夫的状况下最大化的回收垃圾。

正文完
 0