找回密码
 立即注册

QQ登录

只需一步,快速开始

工控课堂 首页 工控文库 上位机编程 查看内容

HashMap的哈希函数为何用(n - 1) & hash

2022-9-18 14:24| 发布者: gk-auto| 查看: 372| 评论: 0|来自: 博客园

摘要: 前言在上一篇Java 中HashMap详解(含HashTable, ConcurrentHashMap)中提到在map.put(key, value)的过程中,计算完key的hash值, 是通过hash (n-1)来得出该元素在Node数组中的下标的,其中n是Node数组的长度。 其实我 ...

前言

在上一篇 Java 中HashMap详解(含HashTable, ConcurrentHashMap)  中提到在map.put(key, value)的过程中,计算完key的hash值, 是通过hash & (n-1)来得出该元素在Node数组中的下标的,其中n是Node数组的长度。 其实我们更容易想到的是hash % n,这样刚好会得到0~n-1之间的数字,可以用作数组下标。那么为何此处是用的位运算呢?

结论

先说结论。 这里有一个前提,那就是HashMap中Node数组的长度始终保持是2^n, 比如默认的16, 如果创建HashMap的时候指定了初始的capacity,而这个capacity可能不是2^n, 会在内部转化一下,得到一个大于这个capacity的最小的2^n的数字来初始化数组。 每次扩容的时候也是进行2倍的扩容。

在这个前提下,hash & (n-1) 与 hash % n 是等价的。 而位运算更快一些。

论证

先来看一组数字:

n  (格式为2^m=十进制数字=二进制数字)n-1 (格式为2^m - 1=十进制数字=二进制数字)
2^2 = 4 = 1002^2 - 1 = 3 = 011
2^3 = 8 = 10002^3 - 1 = 7 = 0111
2^4 = 16 = 100002^4 - 1 = 15 = 01111
2^5 = 32 = 1000002^5 -1 = 31 = 011111

此处我们可以看到规律,2^m的二进制就是1的后面加上m个0, 而2^m -1的二进制就是0的后面加上m个1.

下面我们来看 hash % n(求余数)的运算:

首先看hash/n,由于n=2^m, 我们先看hash/2的情况,这样一来就简单了,因为我们都知道,二进制的情况下,一个数字除以2其实就是右移一位,在左边加一个0,右边移出去一位。如果觉得不好理解,就类比十进制的数字除以10的情况,是一样的。举一反三一下,hash/4的情况自然就是右移2位,由于n=2^m, 其实hash/n的操作就是右移m位

右移之后我们得到的是hash/n的整除,那么余数呢?其实就是我们移出去的数字

举个例子,假设hash = 18, n=4,我们知道18/4=4 , 18%4 =2,看看按照我们上面的运算是否会得到相同的结果:

18=10010, 4=2^2

10010右移2位   0010010
hash=18数组长度n=4=2^218/4得到的整除余数18%4

通过运算可以很容易的验证18/4 = 00100 = 4 , 而18%4 = 10 = 2, 是正确的。

现在假设Node数组进行了扩容n=8,再来看一下:

10010右移3位   00010010
hash=18数组长度n=8=2^318/4得到的整除余数18%8

同样经过运算18 / 8 = 10 = 2, 18 % 8 = 10 = 2, 是正确的。

现在我们可以看到规律, hash % (2^m)的结果, 其实是就是hash这个数字二进制表达的最后m位(被移出去的m位)

而前面我们又知道2^m-1其实就是0后面加上m个1. 还用上面的例子,我们看一下18 & (2^3-1)的运算:

18=10010
2^3-1=00111
与运算00010

我们知道,任何数字与1做与运算,还是得到该数字;任何数字与0做与运算,都得0,那么hash & (2^m-1) ,高位的都是零,只得到低位的m个数字,与上面计算的hash % (2^m)是一样的结果。

证明完成。

关注公众号,加入500人微信群,下载100G免费资料!

最新评论

热门文章
关闭

站长推荐上一条 /1 下一条

QQ|手机版|免责声明|本站介绍|工控课堂 ( 沪ICP备20008691号-1 )

GMT+8, 2025-12-23 02:49 , Processed in 0.072611 second(s), 23 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

返回顶部