HashMap中为何X % length = X & (length - 1)(求余%和与运算&转换问题)

2023-11-18


一、引出问题

在前面讲解 HashMap  的源码实现时,有如下几点:

①、初始容量为 1<<4,也就是24 = 16

 ②、负载因子是0.75,当存入HashMap的元素占比超过整个容量的75%时,进行扩容,而且在不超过int类型的范围时,进行2次幂的扩展(指长度扩为原来2倍)

 扩大一倍

③、新添加一个元素时,计算这个元素在HashMap中的位置,也就是本篇文章的主角 哈希运算。分为三步:

  第一步:取 hashCode 值: key.hashCode()

  第二步:高位参与运算:h>>>16

  第三步:取模运算:(n-1) & hash

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    tab[i = (n - 1) & hash];

ps:第 6 行代码是我自己加的。

  我们知道一个好的 哈希算法能够使得元素分布的更加均匀,从而减少哈希冲突。HashMap 在这块的处理就很巧妙:

  首先第一步取得 hashCode,该方法是一个用native修饰的本地方法,返回的是一个 int 类型的值(根据内存地址换算出来的一个值),通常我们都会重写该方法。

  第二步将取得的哈希值无符号右移16位,高位补0。并与前面第一步获得的hash码进行按位异或^ 运算。这样做有什么用呢?这其实也是扰动函数,为了降低哈希码的冲突。右位移16位,正好是32bit的一半,高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。也就是保证考虑到高低Bit位都参与到Hash的计算中。

  有兴趣的可以看看JDK1.7中,其实是做了4次扰动,在JDK1.8中只做了一次,我猜测是为了在降低冲突的同时保证效率。

本文的重点是第三步,将经过前面两步获取的 hash 值,与HashMap的集合长度减 1 进行按位与 & 运算:(n-1) & hash。但是其实很多哈希算法,为了使元素分布均匀,都是用的取模运算,用一个值去模上总长度,即 n%hash。我们知道在计算机中 & 的效率比 % 高很多,那么如何将 % 转换为 & 运算呢?在HashMap 中,是用的 (n - 1) & hash 进行运算的,那么这是为什么呢?

  这就是本篇博客我们将要明白的问题。

 

二、结论

我们先给出结论:

  当 lenth = 2n 时,X % length = X & (length - 1)

  也就是说,长度为2的n次幂时,模运算 % 可以变换为按位与 & 运算。

  比如:9 % 4 = 1,9的二进制是 1001 ,4-1 = 3,3的二进制是 0011。 9 & 3 = 1001 & 0011 = 0001 = 1

  再比如:12 % 8 = 4,12的二进制是 1100,8-1 = 7,7的二进制是 0111。12 & 7 = 1100 & 0111 = 0100 = 4

  上面两个例子4和8都是2的n次幂,结论是成立的,那么当长度不为2的n次幂呢?

  比如:9 % 5 = 4,9的二进制是 1001,5-1 = 4,4的二进制是0100。9 & 4 = 1001 & 0100 = 0000 = 0。显然是不成立的。

  为什么是这样?下面我们来详细分析。

 

三、分析过程

首先我们要知道如下规则:

  ①、"<<" 左移:右边空出的位上补0,左边的位将从字头挤掉,左移一位其值相当于乘2。

  ②、">>"右移:右边的位被挤掉,右移一位其值相当于除以2。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统。

  ③、">>>"无符号右移,右边的位被挤掉,对于左边移出的空位一概补上0。

  根据二进制数的特点,相信大家很好理解。

  对于给定一个任意的十进制数XnXn-1Xn-2....X1X0,我们将其用二进制的表示方法分解:

  XnXn-1Xn-2....X1X0   = Xn*2n+Xn-1*2n-1+......+X1*21+X0*20                       3-1公式

  这里的十进制数只有三位,同理当有N位时,后面2的幂次方依次从 0 开始递增到 N 。

  回到上面的结论: lenth = 2n 时,X % length = X & (length - 1)

  以及对于除法,被除数是满足分配率的(除数不满足):

  成立:(a+b)÷c=a÷c+b÷c                                                                          3-2公式

  不成立:a÷(b+c)≠a÷c+b÷c

  通过 3-1公式以及 3-2 公式,我们可以得出当任意一个十进制除以一个2k的数时,我们可以将这个十进制转换成3-1公式的表示形式:

  (XnXn-1Xn-2....X1X0)  / 2k   =  (Xn*2n+Xn-1*2n-1+......+X1*21+X0*20) / 2k = Xn*2n /  2k +Xn-1*2n-1 /  2k  +......+  X1*21 /  2k + X0*20 /  2k

  如果我们想求上面公式的余数,相信大家一眼就能看出来:

  ①、当 0<= k <= n 时,余数为 Xk*2k+Xk-1*2k-1+......+X1*21+X0*20   ,也就是说 比 k 大的 n次幂,我们都舍掉了(大的都能整除 2k),比k小的我们都留下来了(小的不能整除2k)。那么留来下来即为余数。

  ②、当 k > n 时,余数即为整个十进制数。

  看到这里,我们离证明结论已经很近了。再回到上面说的二进制的移位操作,向右移 n 位,表示除以 2n 次方,由此我们得到一个很重要的结论:

  一个十进制数对一个2n 的数取余,我们可以将这个十进制转换为二进制数,将这个二进制数右移n位,移掉的这 n 位数即是余数。

  知道怎么算余数了,那么我们怎么去获取这移掉的 n 为数呢?

  我们再看20,21,22....2n  用二进制表示如下:

  0001,0010,0100,1000,10000......

  我们把上面的数字减一:

  0000,0001,0011,0111,01111......

  根据与运算符&的规律,当位上都是 1 时,结果才是 1,否则为 0。所以任意一个二进制数对 2k 取余时,我们可以将这个二进制数与(2k-1)进行按位与运算,保留的即使余数。

  这就完美的证明了前面给出的结论:

  当 lenth = 2n 时,X % length = X & (length - 1)

  注意,一定要是2n次方,才满足上面的公式,否则就是错误的。


总结

通过上面的分析过程了,我们完美了证明了公式的正确性。在回到 HashMap 的实现过程,我们知道HashMap的初始容量为啥是 1<<4 了吧,而且每次扩容都是扩大一倍。因为必须要完美的满足 hash 算法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HashMap中为何X % length = X & (length - 1)(求余%和与运算&转换问题) 的相关文章

  • 如何在日期选择器中设置不在当前月份的单元格的样式

    我目前正在为我的 JavaFX 应用程序制作注册表 问题是 当日期选择器中的单元格不在页面的月份上时 我想让该单元格变灰 让我们看看我当前的日期选择器 我的日期选择器 正如您所看到的 我希望下个月的日期 27 日 28 日 30 日以及 1
  • ElasticBeanstalk Java,Spring 活动配置文件

    我正在尝试通过 AWS ElasticBeanstalk 启动 spring boot jar 一切正常 配置文件为 默认 有谁知道如何为 java ElasticBeanstalk 应用程序 不是 tomcat 设置活动配置文件 spri
  • CXF Swagger2功能添加安全定义

    我想使用 org apache cxf jaxrs swagger Swagger2Feature 将安全定义添加到我的其余服务中 但是我看不到任何相关方法或任何有关如何执行此操作的资源 下面是我想使用 swagger2feature 生成
  • java.io.IOException: %1 不是有效的 Win32 应用程序

    我正在尝试对 XML 文档进行数字签名 为此我有两个选择 有一个由爱沙尼亚认证中心为程序员创建的库 还有一个由银行制作的运行 Java 代码的脚本 如果使用官方 认证中心 库 那么一切都会像魅力一样进行一些调整 但是当涉及到银行脚本时 它会
  • java中删除字符串中的特殊字符?

    如何删除字符串中除 之外的特殊字符 现在我用 replaceAll w s 它删除了所有特殊字符 但我想保留 谁能告诉我我该怎么办 Use replaceAll w s 我所做的是将下划线和连字符添加到正则表达式中 我添加了一个 连字符之前
  • 如何为 Gson 编写自定义 JSON 反序列化器?

    我有一个 Java 类 用户 public class User int id String name Timestamp updateDate 我收到一个包含来自 Web 服务的用户对象的 JSON 列表 id 1 name Jonas
  • 一种使用 Java Robot API 和 Selenium WebDriver by Java 进行文件上传的解决方案

    我看到很多人在使用 Selenium WebDriver 的测试环境中上传文件时遇到问题 我使用 selenium WebDriver 和 java 也遇到了同样的问题 我终于找到了解决方案 所以我将其发布在这里希望对其他人有所帮助 当我需
  • 如何在jsp代码中导入java库?

    我有以下jsp代码 我想添加 java io 等库 我怎样才能做到这一点
  • OnClick 事件中的 finish() 如何工作?

    我有一个Activity一键退出Activity 通过layout xml我必须设置OnClick事件至cmd exit调用 this finish 效果很好 public void cmd exit View editLayout thi
  • Clip 在 Java 中播放 WAV 文件时出现严重延迟

    我编写了一段代码来读取 WAV 文件 大小约为 80 mb 并播放该文件 问题是声音播放效果很差 极度滞后 你能告诉我有什么问题吗 这是我的代码 我称之为doPlayJframe 构造函数内的函数 private void doPlay f
  • 序列化对象以进行单元测试

    假设在单元测试中我需要一个对象 其中所有 50 个字段都设置了一些值 我不想手动设置所有这些字段 因为这需要时间而且很烦人 不知何故 我需要获得一个实例 其中所有字段都由一些非空值初始化 我有一个想法 如果我要调试一些代码 在某个时候我会得
  • 从 android 简单上传到 S3

    我在网上搜索了从 android 上传简单文件到 s3 的方法 但找不到任何有效的方法 我认为这是因为缺乏具体步骤 1 https mobile awsblog com post Tx1V588RKX5XPQB TransferManage
  • Spring Data 与 Spring Data JPA 与 JdbcTemplate

    我有信心Spring Data and Spring Data JPA指的是相同的 但后来我在 youtube 上观看了一个关于他正在使用JdbcTemplate在那篇教程中 所以我在那里感到困惑 我想澄清一下两者之间有什么区别Spring
  • 检查 protobuf 消息 - 如何按名称获取字段值?

    我似乎无法找到一种方法来验证 protobuf 消息中字段的值 而无需显式调用其 getter 我看到周围的例子使用Descriptors FieldDescriptor实例到达消息映射内部 但它们要么基于迭代器 要么由字段号驱动 一旦我有
  • Java直接内存:在自定义类中使用sun.misc.Cleaner

    在 Java 中 NIO 直接缓冲区分配的内存通过以下方式释放 sun misc Cleaner实例 一些比对象终结更有效的特殊幻像引用 这种清洁器机制是否仅针对直接缓冲区子类硬编码在 JVM 中 或者是否也可以在自定义组件中使用清洁器 例
  • 将多模块 Maven 项目导入 Eclipse 时出现问题 (STS 2.5.2)

    我刚刚花了最后一个小时查看 Stackoverflow com 上的线程 尝试将 Maven 项目导入到 Spring ToolSuite 2 5 2 中 Maven 项目有多个模块 当我使用 STS 中的 Import 向导导入项目时 所
  • Tomcat 6找不到mysql驱动

    这里有一个类似的问题 但关于类路径 ClassNotFoundException com mysql jdbc Driver https stackoverflow com questions 1585811 classnotfoundex
  • 将 JSON 参数从 java 发布到 sinatra 服务

    我有一个 Android 应用程序发布到我的 sinatra 服务 早些时候 我无法读取 sinatra 服务上的参数 但是 在我将内容类型设置为 x www form urlencoded 之后 我能够看到参数 但不完全是我想要的 我在
  • Java - 不要用 bufferedwriter 覆盖

    我有一个程序可以将人员添加到数组列表中 我想做的是将这些人也添加到文本文件中 但程序会覆盖第一行 因此这些人会被删除 如何告诉编译器在下一个空闲行写入 import java io import java util import javax
  • com.jcraft.jsch.JSchException:身份验证失败

    当我从本地磁盘上传文件到远程服务器时 出现这样的异常 com jcraft jsch JSchException Auth fail at org apache tools ant taskdefs optional ssh Scp exe

随机推荐

  • spacemacs创建layer

    m x configuration layer create layer 选择目录 默认是private 然后命名layername 比如my 在 spacemacs文件里 增加my到layer list里 在private my pack
  • 马踏棋盘算法(骑士周游问题)

    将马随机放在国际象棋的8 8棋盘的某个方格中 马按走棋规则进行移动 要求每个方格只进入一次 走遍棋盘上全部64个方格 代码 include stdafx h include
  • HackPorts – Mac OS X 渗透测试框架与工具

    HackPorts是一个OS X 下的一个渗透框架 HackPorts是一个 超级工程 充分利用现有的代码移植工作 安全专业人员现在可以使用数以百计的渗透工具在Mac系统中 而不需要虚拟机 工具列表 0trace 3proxy Air Au
  • 调用 matlab的function函数出现未定义函数的现象

    调用 matlab的function函数出现未定义函数的现象 将matlab的默认位置 C Users Administrator Desktop 改为当前文件所在位置即可 具体参考 链接 https blog csdn net wzgl
  • 2019-12-28

    c语言 入门级别代码解一元二次方程 其实实现输入a b c解出x的值并不难 首先我们先要了解一元二次方程的解法 将步骤一步步套入程序 include
  • SpringSecurity一日干

    前后端登录校验的逻辑 完整流程 本质就是过滤器链 1 提交用户名和密码 2 将提交的信息封装Authentication对象 3 传给下一个 调用2中的authenticate方法进行验证 4 3步骤也验证不了需要调用3的authentic
  • 【计算机组成原理】总线宽度和总线带宽的区别,总线带宽的计算

    总线宽度 总线的宽度 指总线在单位时间内可以传输的数据总数 即平常说的32位 64位 总线宽度 总线位宽 数据线的根数 总线带宽 总线带宽 指总线在单位时间内可以传输的数据总数 等于总线的宽度与工作频率的乘积 通常单位 MB s MBps
  • vscode修改插件的安装的位置,从c盘转移到其他盘

    作为一个电脑非常落后的人 c盘每MB位置都很珍贵 能安装到别的盘的就尽量安装到其他盘 首先在c盘找到 vscode文件 下面的extensions文件就是插件放置的位置 将extensions里的文件全部剪切到自己定义的位置下 原来的ext
  • Quartz 体系结构

    Quartz的体系结构 Quartz的重要组件 Scheduler 用于与调度程序交互的主程序接口 Scheduler调度程序 任务执行计划表 只有安排进执行计划的任务Job 通过scheduler scheduleJob方法安排进执行计划
  • TFT-LCD显示屏工作原理图文解析

    一直很好奇手机屏幕的显示原理 这是LCD的 OLED 屏幕的与此不同 直接贴上原文链接 http www 58display com article zixun 208 html 以下是复制的原文 液晶显示器是什么 不同的应用环境 有不同的
  • C++-对四个智能指针:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

    回答如下 C 的智能指针是一种特殊类型的 指针 其主要目的是自动跟踪内存分配和释放 以避免程序中出现内存泄露或空悬指针等问题 主要采用的技术是 借助于类的生命周期 当超出了类的作用域时 类对象会自动调用析构函数 然后就可以释放内存等资源 无
  • Mac M1安装Homebrew 简单实用

    1 首先创建安装目录 sudo mkdir p opt homebrew 2 将目录属主修改为当前用户 方便直接实用brew install sudo chown R whoami opt homebrew 3 进入 opt文件夹 cd o
  • 第08章 Spring-Boot 使用简介

    第08章 Spring Boot 简介 Spring框架功能很强大 但是就算是一个很简单的项目 我们也要配置很多东西 因此就有了Spring Boot框架 它的作用很简单 就是帮我们自动配置 Spring Boot框架的核心就是自动配置 只
  • 轻量级自动化测试框架WebZ

    一 什么是WebZ WebZ是我用Python写的 关键字驱动 的自动化测试框架 基于WebDriver 设计该框架的初衷是 用自动化测试让测试人员从一些简单却重复的测试中解放出来 之所以用 关键字驱动 模式是因为我觉得这样能让测试人员 测
  • 数据库中索引会失效的几种情况(oracle)

    文章目录 数据库中索引会失效的几种情况 oracle 1 没有 WHERE 子句 2 使用 IS NULL 和 IS NOT NULL 3 WHERE 子句中使用函数 4 使用 LIKE T 进行模糊查询 5 WHERE 子句中使用不等于操
  • 输入两个正整数,输出它们的最大公约数和最小公倍数

    include
  • python 列表元组字典集合相关知识

    python 数据类型 列表 可变数据类型 列表的创建 或者 list 列表的索引 由下标0开始 最后一个为 1 列表的切片 list start end step 列表的计算 支持 等方法 列表的方法 格式 列表名称 方法名字 index
  • 如何结束8080端口的进程

    1 找到8080端口进程 win r 输入cmd打开终端窗口 输入netstat aon findstr 8080 找出所有的进程 2 结束对应的进程 taskkill F PID 53408
  • tinymce 去掉编辑器换行默认增加的p标签

    问题 tinymce 编辑器里面使用回车换行后会自动添加p标签 解决方法 增加forced root block这个属性 替换为空后 换行就没有p标签了 格式 forced root block 删除在tinymce中自动添加的p标签 如下
  • HashMap中为何X % length = X & (length - 1)(求余%和与运算&转换问题)

    目录 一 引出问题 二 结论 三 分析过程 总结 一 引出问题 在前面讲解 HashMap 的源码实现时 有如下几点 初始容量为 1 lt lt 4 也就是24 16 负载因子是0 75 当存入HashMap的元素占比超过整个容量的75 时