HashMap的哈希函数为何用(n - 1) & hash
前言
在上一篇 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 = 100 | 2^2 - 1 = 3 = 011 |
| 2^3 = 8 = 1000 | 2^3 - 1 = 7 = 0111 |
| 2^4 = 16 = 10000 | 2^4 - 1 = 15 = 01111 |
| 2^5 = 32 = 100000 | 2^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
| 1 | 0 | 0 | 1 | 0 | 右移2位 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
| hash=18 | 数组长度n=4=2^2 | 18/4得到的整除 | 余数18%4 | |||||||||
通过运算可以很容易的验证18/4 = 00100 = 4 , 而18%4 = 10 = 2, 是正确的。
现在假设Node数组进行了扩容n=8,再来看一下:
| 1 | 0 | 0 | 1 | 0 | 右移3位 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
| hash=18 | 数组长度n=8=2^3 | 18/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= | 1 | 0 | 0 | 1 | 0 |
| 2^3-1= | 0 | 0 | 1 | 1 | 1 |
| 与运算 | 0 | 0 | 0 | 1 | 0 |
我们知道,任何数字与1做与运算,还是得到该数字;任何数字与0做与运算,都得0,那么hash & (2^m-1) ,高位的都是零,只得到低位的m个数字,与上面计算的hash % (2^m)是一样的结果。
证明完成。
HashMap的哈希函数为何用(n - 1) & hash的更多相关文章
- 【C# 集合】Hash哈希函数 |散列函数|摘要算法
希函数定义 哈希函数(英語:Hash function)又称散列函数.散列函数.摘要算法.单向散列函数.散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来.该函数将数据打乱混合,重新 ...
- HashMap中的哈希函数分析
首先我们要知道,在理想情况下的哈希表中,哈希函数生成的哈希值是value在数组中的下标,其范围是分布于负无穷到正无穷的整个实整数轴的.而在现实情况下,是不可能存在这么大的一个数组的.接下来分析Hash ...
- 算法初级面试题05——哈希函数/表、生成多个哈希函数、哈希扩容、利用哈希分流找出大文件的重复内容、设计RandomPool结构、布隆过滤器、一致性哈希、并查集、岛问题
今天主要讨论:哈希函数.哈希表.布隆过滤器.一致性哈希.并查集的介绍和应用. 题目一 认识哈希函数和哈希表 1.输入无限大 2.输出有限的S集合 3.输入什么就输出什么 4.会发生哈希碰撞 5.会均匀 ...
- HashMap分析 + 哈希表
http://www.cnblogs.com/hzmark/archive/2012/12/24/HashMap.html http://www.cnblogs.com/xqzt/archive/20 ...
- 左神算法第五节课:认识哈希函数和哈希表,设计RandomPool结构,布隆过滤器,一致性哈希,岛问题,并查集结构
认识哈希函数和哈希表 MD5Hash值的返回范围:0~9+a~f,是16位,故范围是0~16^16(2^64)-1, [Hash函数],又叫散列函数: Hash的性质: 1) 输入域无穷大: 2) ...
- 字符串哈希函数(String Hash Functions)
哈希函数举例 http://www.cse.yorku.ca/~oz/hash.html Node.js使用的哈希函数 https://www.npmjs.org/package/string-has ...
- lintcode:哈希函数
题目: 哈希函数 在数据结构中,哈希函数是用来将一个字符串(或任何其他类型)转化为小于哈希表大小且大于等于零的整数.一个好的哈希函数可以尽可能少地产生冲突.一种广泛使用的哈希函数算法是使用数值33,假 ...
- Eight(bfs+全排列的哈希函数)
Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 22207 Accepted: 9846 Special Judge ...
- lintcode-->哈希函数
在数据结构中,哈希函数是用来将一个字符串(或任何其他类型)转化为小于哈希表大小且大于等于零的整数.一个好的哈希函数可以尽可能少地产生冲突.一种广泛使用的哈希函数算法是使用数值33,假设任何字符串都是基 ...
随机推荐
- 所有人都说Python 简单易学,为何我觉得难?
来谈谈心 记得刚学Python的时候,几乎所有人都说Python 简单易学,而对于编程零基础,只掌握Word和Excel的人来说,感觉真的好难. 学习之前网上的教材看了,Python的书也看了,包括& ...
- FileNameFilter过滤器的使用和Lambda优化程序--IO概述(概念&分类)
FileNameFilter过滤器的使用和Lambda优化程序 public class Demo02Filter { public static void main(String[] args) { ...
- 项目中使用@Transactional需要注意的点
项目如果是Spring Boot.或者Spring Cloud,切记需要在启动类上加入@EnableTransactionManagement该注解.否则事务不生效. @Transactional是一 ...
- Android Studio的初次认识
Android的初试 一.认识Android Studio 在我们新建项目的时候,会遇到这样的一个窗口,首先我们认识一下这些都是什么,这样我们才能够更好的进行下一步的学习! 这里的 Phone and ...
- antd vue 折叠面板 v-for 循环点击无效
问题描述 实现一个折叠面板点击展开,但是必须点击两次才能展开,第一次无效 <a-collapse-panel v-for="(item, index) in dataMap" ...
- CenetOS 7设置IP
使用ip addr查看网卡名称,如:eth0 vim /etc/sysconfig/network-scripts/ifcfg-eth0,编辑网卡名对应的配置文件: BOOTPROTO=dhcp ON ...
- SElinux管理
SElinux: 是Linux的一个强制访问控制的安全模块 SElinux的相关概念: 对象:文件.目录.进程.端口等 主体:进程称为主体 SElinux将所有的文件都赋予一个type类型的标签,所有 ...
- MySQL 常见索引类型介绍
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. MySQL 主要索引类型有如下几种: 1.主键索引 2.唯一索引 3.普通索引 4.空间索引 5.全文索引 假设有如下一张 ...
- 在django中前后端传输数据的编码格式(contentType)
写在前面 在django中,针对前后端传输数据的编码格式,我们主要研究的是post请求:因为get请求传输的数据往往是直接放在url的后面的!如: url?username=zhang&pas ...
- Luogu1099 树网的核 (暴力?,floyd?)(还未想正解,暴力就A了)
阅读理解两小时,手敲暴力思考5分钟.然后\(n^3\)就A了 暴力代码 #include <iostream> #include <cstdio> #include <c ...