HashMap中哈希表的长度为什么需要是2的幂次方以及怎么实现
看过HashMap源码的人可能都用印象,就是hashMap的哈希表长度可以由自己指定也可以不指定使用默认长度,但是如果在了解或者发现tableSizeFor方法的话,你就会知道此方法会改变我们的输入长度 (如果我们输入15,他会改为16),那么他为什么要修改我们设置的长度,以及修改后有什么作用?带着这个疑问我们往下看;
1. HashMap 的长度为什么需要是2的幂次方
为了能让hashMap存取高效,尽量减少碰撞,也就是要尽量把数据分配均匀。
Hash值的取值范围-2147483648到2147483647,总共有40+亿个映射空间,只要哈希函数映射的比较均匀,一般应用很难出现碰撞,但是内存肯定不能一次加载这么长的数组,所以这个散列值是不能拿来直接用的,我们只能创建合理长度的数组作为哈希表,在插入数据之前做取模运算,得到的余数就是将要存放的数据在哈希表中对应的下标。在HashMap中这个下标的取值算法是:(n - 1) & hash n是哈希表的长度。
取模运算中如果除数是2的幂次方则等价于 其与除数减一的&操作,就是:hash % length == hash & (length - 1)
采用二进制位操作 & 相对于 % 能够提高运算效率,这也就解释了为啥HashMap的长度需要为2的幂次方
2. HashMap怎么实现的
先看下JDK8的源码:
/**
* 方法保证了HashMap的哈希表长度总位2的幂次方
* 返回大于输入参数且最近的2的整数次幂的数
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
我也是看到这个方法后才决定写这篇博文来记录一下我的感想,我已经不知道多少次被源码作者所折服,我甚至觉得这才是代码应该的样子;
结果分析
n第一次右移一位时,相当于将最高位的1右移一位,再和原来的n取或,就将最高位和次高位都变成1,也就是两个1;
第二次右移两位时,将最高的两个1向右移了两位,取或后得到四个1;
依次类推,右移16位再取或就能得到32个1;
你是不是不知道要32个1干嘛?自己体会如下:
| 2的幂次方 | 二进制表示 | 十进制标识 |
|---|---|---|
| 2^0 | 1 | (1-1)+1 |
| 2^1 | 10 | (2-1)+1 |
| 2^2 | 100 | (4-1)+1 |
| 2^3 | 1000 | (8-1)+1 |
| 2^4 | 10000 | (16-1)+1 |
| 2^5 | 100000 | (32-1)+1 |
二进制数字如果都是1的话,那么他加一后就是首位为1其他位都是0,这个数字肯定是2的幂次方, 2^n == 1<<n
举例说明:
10的二进制是1010,减1就是1001
第一次右移取或: 1001 | 0100 = 1101 ;
第二次右移取或: 1101 | 0011 = 1111 ;
第三次右移取或: 1111 | 0000 = 1111 ;
第四次第五次同理
最后得到 n = 1111 ,返回值是 n+1 = 2 ^ 4 = 16 ;
自己实验一把
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 2;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 4;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 8;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 16;
System.out.println(Integer.toBinaryString(n));
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
public static void main(String[] args) {
int a = 65538;
System.out.println(Integer.toBinaryString(a));
int i = tableSizeFor(a);
System.out.println(i);
}
执行结果:
10000000000000010
11000000000000001
11110000000000001
11111111000000001
11111111111111111
11111111111111111
131072
看到这里是不是也有中被折服的感觉?那就对了,路还长,编码还要继续,且看且学习。。。 _
HashMap中哈希表的长度为什么需要是2的幂次方以及怎么实现的更多相关文章
- js中哈希表的几种用法总结
本篇文章只要是对js中哈希表的几种用法进行了总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 1. <html> <head> <script type=" ...
- Java中哈希表(Hashtable)是如何实现的
Java中哈希表(Hashtable)是如何实现的 Hashtable中有一个内部类Entry,用来保存单元数据,我们用来构建哈希表的每一个数据是Entry的一个实例.假设我们保存下面一组数据,第一列 ...
- C#中哈希表与List的比较
简单概念 在c#中,List是顺序线性表(非链表),用一组地址连续的存储单元依次存储数据元素的线性结构. 哈希表也叫散列表,是一种通过把关键码值映射到表中一个位置来访问记录的数据结构.c#中的哈希表有 ...
- C++ STL中哈希表Map 与 hash_map 介绍
0 为什么需要hash_map 用过map吧?map提供一个很常用的功能,那就是提供key-value的存储和查找功能.例如,我要记录一个人名和相应的存储,而且随时增加,要快速查找和修改: 岳不群-华 ...
- HashMap分析 + 哈希表
http://www.cnblogs.com/hzmark/archive/2012/12/24/HashMap.html http://www.cnblogs.com/xqzt/archive/20 ...
- [转]net中哈希表的使用 Hashtable
本文转自:http://www.cnblogs.com/gsk99/archive/2011/08/28/2155988.html 以下是PetShop中DBHelper中的使用过程: //创建哈希表 ...
- C#中哈希表(HashTable)的用法详解以及和Dictionary比较
1. 哈希表(HashTable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似keyvalue的键值对, ...
- MySQL中哈希表
也称为散列表 由直接寻址表改进而来.先看直接寻址表 当关键字的全域U比较小时,直接寻址是一种简单而有效的技术.加入某应用要用到一个动态集合,其中每个元素都有一个取自全域U={0,1,...,m-1}的 ...
- 转 C#中哈希表(HashTable)的用法详解
看了一遍有关哈希表的文字,作者总结的真是不错 .收藏起来 1. 哈希表(HashTable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提 ...
- C#中哈希表(HashTable)的用法详解
描述: 哈希表存放 key.values ,key值可以用于快速调取用,values 对应object类型,也就是说所有类型. 实例: 1.HashTable存放学生的成绩 Hashtable ht1 ...
随机推荐
- 关于Java后台处理前端用户传来数据过大,超过数据库字段设置长度导致的异常问题处理
在DTO bean对象上使用javax验证(如最小值,最大值等,请参阅here).
- HTTP相关返回值异常如何解决(下篇)
今天我们讲讲HTTP相关返回值异常如何解决(实例持续更新中) 一.4xx客户端错误状态码 这些状态码表示请求有问题,通常是由于客户端的错误引起的. 1.1 400 Bad Request: 请求 ...
- computed methods watch filters
computed(计算属性) 计算属性该属性里面的方法必须要有return返回值,这个返回值就是(value值). 有几个关键点 1) 计算后属性不需要在data中重复定义 2) 计算后属性必须渲染后 ...
- Redis中的分布式锁(步步为营)
分布式锁 概述 分布式锁指的是,所有服务中的所有线程都去获取同一把锁,但只有一个线程可以成功的获得锁,其他没有获得锁的线程必须全部等待,直到持有锁的线程释放锁. 分布式锁是可以跨越多个实例,多个进程的 ...
- Linux之定时任务crontab
一.cron.d增加定时任务 当我们要增加全局性的计划任务时,一种方式是直接修改/etc/crontab.但是,一般不建议这样做,/etc/cron.d目录就是为了解决这种问题而创建的. 例如,增加一 ...
- gitlab之配置文件.gitlab-ci.yml
自动化部署給我们带来的好处 自动化部署的好处体现在几个方面 1.提高前端的开发效率和开发测试之间的协调效率 Before 如果按照传统的流程,在项目上线前的测试阶段,前端同学修复bug之后,要手动把代 ...
- Java8 Lambda编程常用技巧
遍历打印List List<Integer> list= Arrays.asList(1,5,6,8,9,32,5,8,7,4,5); list.forEach(System.out::p ...
- JDBC基础知识
常见连接数据库工具: 图形化工具:点击.拖拽就可以操作数据库,对用户友好,简单对数据操作,复杂数据库操作爱莫能助 JDBC(驱动程序):调用jar包接口 窗口(命令行):输入完整SQL语句对复杂数据库 ...
- 再用RNN神经网络架构设计生成式语言模型
上一篇:<用谷歌经典ML方法方法来设计生成式人工智能语言模型> 序言:市场上所谓的开源大语言模型并不完全开源,通常只提供权重和少量工具,而架构.训练数据集.训练方法及代码等关键内容并未公开 ...
- C# Linq 的三种去重方式(Distinct)
前言 关于C#中默认的Distinct方法在什么情况下才能去重,这个就不用我再多讲,针对集合对象去重默认实现将不再满足,于是乎我们需要自定义实现来解决这个问题,接下来我们详细讲解几种常见去重方案,孰好 ...