HashMap中Hash算法的应用

算法一直是我的弱项,然而面试中基本是必考的项目,刚好上次看到一个HashMap的面试题,今天也来学习下 HashMap中的hash算法是如何实现的。

数学知识回顾
<< : 左移运算符,num << 1,相当于num乘以2 低位补0
举例:3 << 2
将数字3左移2位,将3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011,然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,最后在低位(右侧)的两个空位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12。
数学意义:
在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。

>>: 右移运算符
举例:11 >> 2
则是将数字11右移2位,11 的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 0010。转换为十进制是3。
数学意义:
右移一位相当于除2,右移n位相当于除以2的n次方。这里是取商哈,余数就不要了。

>>> : 无符号右移,忽略符号位,空位都以0补齐
按二进制形式把所有的数字向右移动对应位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。 其他结构和>>相似。

% : 模运算 取余
简单的求余运算

^ : 位异或 第一个操作数的的第n位于第二个操作数的第n位相反,那么结果的第n为也为1,否则为0
0^0=0, 1^0=1, 0^1=1, 1^1=0

& : 与运算 第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
0&0=0, 0&1=0, 1&0=0, 1&1=1

| : 或运算 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
0|0=0, 0|1=1, 1|0=1, 1|1=1

~ : 非运算 操作数的第n位为1,那么结果的第n位为0,反之,也就是取反运算(一元操作符:只操作一个数)
~1=0, ~0=1

哈希算法(Hash)
又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

哈希算法最重要的特点就是:

相同的输入一定得到相同的输出;
不同的输入大概率得到不同的输出。
哈希算法的目的就是为了验证原始数据是否被篡改。

HashMap位运算

此时我们心中会有两个疑惑:

为什么要无符号右移 16 位后做异或运算
key 本身的 hashCode 直接拿来用不行吗
来看这样一个例子:
在这里插入图片描述
将 h 无符号右移 16 为相当于将高区 16 位移动到了低区的 16 位,再与原 hashcode 做异或运算,可以看作是将高低位二进制特征混合起来。

从上图中可以看出,高位的 16 位与原 hashcode 相比没有发生变化,低位的 16 位发生了变化。

上面的 (h = key.hashCode ()) ^ (h >>> 16) 进行运算后,可以把高区与低区的二进制特征混合到低区,那么为什么要这么做呢?

我们要知道,上面计算出来的hashcode值接下来要参与到hashmap中数组槽位的计算,其计算公式是:(n - 1) & hash,现在假设数组槽位大小是16,那么槽位计算过程如下:
在这里插入图片描述
观察可以看出,如果我们不做刚才移位异或运算,那么在计算槽位时将丢失高区特征。也许你可能会说,即使丢失了高区特征,不同 hashcode 也可以计算出不同的槽位来,但是细想当两个哈希码很接近时,那么这高区的一点点差异就可能导致一次哈希碰撞,所以这也是将性能做到极致的一种体现。

为什么要采用异或运算
异或运算能更好的保留各部分的特征,如果采用 & 运算计算出来的值会向 1 靠拢,采用 | 运算计算出来的值会向 0 靠拢。

为什么槽位数必须使用 2^n
这里假设槽位数不是 16,而是 17,那么槽位计算公式就变成:(17 - 1) & hash。
在这里插入图片描述
原因:可以看出计算结果将会大大趋同,hashcode 参加 & 运算后被更多位的 0 屏蔽,计算结果只剩下两种,分别是0 和 16,这对于 hashmap 来说是一种灾难。
如果想让 Hash 结果分布更加均匀,首先想到的就是使用取余(%)操作。重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash % length == hash & (length - 1) 的前提是 length 是 2 的 n 次方)。” 并且采用二进制位操作 &,相对于 % 能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值