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 ...
随机推荐
- Spring SPI、Solon SPI 有点儿像(Maven 与 Gradle)
一.什么是 SPI SPI 全名 Service Provider interface,翻译过来就是"服务提供接口".基本效果是,申明一个接口,然后通过配置获取它的实现,进而实现动 ...
- 说说Java的类加载机制?究竟什么是双亲委派模型?
首先引入一个概念,什么是Java类加载器? 一句话总结:类加载器(class loader)用来加载 Java 类到 Java 虚拟机中. 官方总结:Java类加载器(英语:Java Classloa ...
- ATC:多快好省,无参数token reduction方法 | ECCV'24
来源:晓飞的算法工程笔记 公众号,转载请注明出处 论文: Agglomerative Token Clustering 论文地址:https://arxiv.org/abs/2409.11923 论文 ...
- 100GbE 网卡到底有多快?Mellanox CX455-ECAT QSFP28 100Gbps 带宽测试
地址: https://www.youtube.com/watch?v=iqQGWsH6F0I
- 【Playwright + Python】系列(十)利用 Playwright 完美处理 Dialogs 对话框
哈喽,大家好,我是六哥!今天我来给大家分享一下如何使用playwight处理Dialogs对话框,面向对象为功能测试及零基础小白,这里我尽量用大白话的方式举例讲解,力求所有人都能看懂,建议大家先**收 ...
- Swagger注解中带有“/”导致SwaggerUI显示异常
日常开发中一直使用swagger作为接口文档工具使用,这次在使用过程中发现一个问题. 正常情况下显示如下图 代码配置如下图 Controller Model 如果在Swagger注解中使用" ...
- 如何制作一个HTML页面的锁屏功能
如果后台一些界面比较敏感,希望主动或者被动的在人员不想暴露信息的情况下加一把锁,就是说避免信息一直在页面上暴露,可以使用"阅后即焚"这种思路,这种思路比较简单,显示了就过几秒删除, ...
- VAE变分自编码器Keras实现
变分自编码器(variational autoencoder, VAE)是一种生成模型,训练模型分为编码器和解码器两部分. 编码器将输入样本映射为某个低维分布,这个低维分布通常是不同维度之间相互独立的 ...
- 本地环境搭建Virtualbox+Vagrant
环境准备 virtualbox是免费,不必要费劲去找破解,下载就可以用. 使用virtualbox每次安装虚拟机,需要你去下载iso,然后设置虚拟机硬件配置,使用iso创建虚拟器.一系列的手工操作,如 ...
- Springboot集成WebSocket实现智能聊天【Demo】
背景 openai 目前越来越流行,其他 ai 产业也随之而来,偶然翻到 openai接口文档,就想着可以调用接口实现智能聊天,接下来就写写我怎么接入 websocket 的过程,文笔不佳,谅解. 接 ...