1、使用位运算(&)来实现取模运算(%)

在jdk1.7源码中,hashmap中有一方法,叫做indexFor(),该方法其实主要是将hash生成的整型转换成链表数组中的下标。我们理所应当的应该认为会使用key的hash值对着数组总长度取模得到所引下标,但它的代码是这样的:

return h & (length-1)

那这段代码是什么意思呢?其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

X % 2^n = X & (2^n – 1)

2n表示2的n次方,也就是说,一个数对2n取模 == 一个数和(2^n – 1)做按位与运算 。

假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 = 7 ,即0111。

此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。

从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。

6 % 8 = 6 ,6 & 7 = 6

10 & 8 = 2 ,10 & 7 = 2

所以,return h & (length-1);只要保证length的长度是2^n的话,就可以实现取模运算了。而HashMap中的length也确实是2的倍数,初始值是16,之后每次扩充为原来的2倍。

2、扩容时,元素的位置迁移

我们知道,hashmap在元素个数达到 元素个数阈值*加载因子 的时候,会进行扩容操作,扩容一定意味着之前的元素需要发生位置的迁移。

而面对元素的迁移,结合1,我们不难想到用来替代取模运算的&运算重新对每一个元素进行重新散列计算所在索引位置。

但实际上,这种操作也并非是最优解,还有什么办法呢? 那就是只需看该元素的hash值二进制位上与此次扩容后长度-1(2^n-1)的二进制位上的最高位对应的那位是0还是1,如果是1,则不需要变化,因为1 & 1还是1,如果是0 ,则只需要让索引值+新多出来的那1位的值。

举个栗子:

有一个元素X,hash后的值为29,二进制为: 0001 1101
所要放入的map的内部数组长度为16,加载因子为0.75
此时根据上述位运算求索引的方法,使用hash(X) & 数组.length-1,
29==> 0001 1101
&
16-1=15==> 0000 1111
0000 1101
由此可以得出,求出的索引值为13

当随着元素的不断增加,达到了数组扩容的阈值: 16x0.75=12
此时,对数组进行2倍扩容,变成长度为32的数组。
然后将对原有元素进行移动位置,以上述X为例,

先使用我们2中总结出来的快捷算法来进行计算:

原索引值为13
32比16在二进制上多出的那一位,也就是16

此时,我们算出X元素在扩容后的位置应当是 13+16=29

接下来,我们使用重新&运算来进行计算,
12==> 0001 1101
&
32-1=31==> 0001 1111
0001 1101
由此可以得出,求出的索引值为29

并且从&运算的运算过程中也可以看到,X原有位上(后4位)数值不管是和16比还是和32比,都是没有变化的,因为不管是16还是32,这几位上面都是1。
而唯一可能会发生变化的,则是取决于X对应的新一位上的数是0还是1,是0的话,数压根就不会变。 是1的话, 只需要原有值+加上这多出来的1位的值就可以得到新的散列值了

但不得不提,这一切,都要得益于只有长度在2的n次幂的情况下才能够使用。

那hashmap里是怎么应用的呢?
关于2的源码部分:

在jdk1.8中的hashmap源码中,就运用了这个技巧来对resize后的元素索引重排进行优化。
核心代码是:e.hash & oldCap== 0
通过该逻辑将旧数组里的每一个链表元素分为两部分,上述代码==0和!=0的,之后 ==0的在新数组的位置不变,!=0的 用原有索引值+老的数组长度 得到的索引就是它的新位置

那为什么可以用这个逻辑来决定元素到底需不需要改变索引值呢?通过之前的描述,我们其实只需要知道扩容后新增的那位上,该元素的值到底是0还是1, 那如何得到这个值?
e.hash & oldCap就可以得到!
也就是 元素hash &原有数组的长度
此时,数组长度二进制位上,低位都为0,高位为1
此时,元素的hash的与高位的对应位与高位进行&运算,自然知道是0还是1!

沿用之前的例子,有一个元素X,hash后的值为29(e.hash),二进制为: 0001 1101
所要放入的map的内部数组长度为16(oldCap)
此时使用上面的算法,过程:
29==>0001 1101
16==>0001 0000
0001 0000
可知结果是1。

参阅:深入理解hashmap

hashmap为什么长度要是2的n次幂相关推荐

  1. 为什么HashMap的长度一定是2的次幂?

    HashMap是面试过程中最常问的知识点之一 今天用最通俗易懂的大白话来讲一讲:为什么HashMap的长度一定是2的次幂? 大家知道HashMap中,如果想存入数据,首先它需要根据key的哈希值去定位 ...

  2. HashMap 的长度为什么是 2 的幂次方?

    HashMap 的长度为什么是 2 的幂次方? 为了能让HashMap存取高效,尽量减少碰撞,需要将散列表的数据分配均匀.使用HashMap查询或插入数据时,需要先对数组长度取模运算,index = ...

  3. HashMap 的长度为什么是 2 的幂次方

    HashMap 的长度为什么是 2 的幂次方 为了让HashMap存取高效,数据分配均匀从而减少碰撞. 因为将数据存储到链表中的算法是hash&(length-1),length是2的n次方, ...

  4. Hashmap链表长度为8时转换成红黑树,你知道为什么是8吗

    为什么要转换? 每次遍历一个链表,平均查找的时间复杂度是 O(n),n 是链表的长度.红黑树有和链表不一样的查找性能,由于红黑树有自平衡的特点,可以防止不平衡情况的发生,所以可以始终将查找的时间复杂度 ...

  5. 被替换的项目不是替换值长度的倍数_面试官,为啥HashMap的长度是2的n次方?

    前言 HashMap的主干是一个数组,假设我们有3个键值对dnf:1,cf:2,lol:3,每次放的时候会根据hash函数来确定这个键值对应该放在数组的哪个位置,即index = hash(key) ...

  6. Java 集合List、Set、HashMap操作一(Array转List、Set排序、HashMap遍历、Set遍历、List遍历、HashMap大小长度、List打乱顺序)

    数组转集合(Array转List) import java.util.*; import java.io.*;public class ArrayToCollection{public static ...

  7. HashMap初始化长度设置大小

    HashMap的优化点,创建HashMap时,如果已经知道大概要放多少的数据量,可以自己设置好长度,减少扩容,提高速度. 代码比较,存放3个数. 不设置初始化大小 private static voi ...

  8. 为什么HashMap链表长度超过8会转成树结构

    HashMap在JDK1.8及以后的版本中引入了红黑树结构,若桶中链表元素个数大于等于8时,链表转换成树结构:若桶中链表元素个数小于等于6时,树结构还原成链表.因为红黑树的平均查找长度是log(n), ...

  9. HashMap的长度为什么是2的N次方

    1.减小哈希冲突概率 假如当前Entry数组长度为len,插入节点时,需要对key的hashcode进行二次哈希,然后跟len-1相与(得到的值一定小于len,避免数组越界) 如果len是2的N次方, ...

最新文章

  1. 【Netty】mmap 和 sendFile 零拷贝原理
  2. mahout基于Hadoop的CF代码分析(转)
  3. hackme_Login As Admin 0
  4. 第三次学JAVA再学不好就吃翔(part11)--基础语法之switch语句
  5. spine 导出纹理_Spine 纹理打包Texture packing_官方文档中文版
  6. 设计模式C++实现(7)——装饰模式
  7. c# 替换html注释,C# 替换div标签
  8. mysql怎么避免联合查询_mysql-联合查询,连接查询
  9. IIS中启用ASP并连接Access数据库的解决办法
  10. 微软超融合私有云测试02-测试架构描述
  11. JavaScript学习笔记 及 JAVAScript优化
  12. 10GE DWDM SFP+彩色光模块应用案例
  13. android 模拟menu键点击事件,android处理Back键Home键和Menu键事件(转)
  14. win10小课堂:如何彻底关闭windows defender
  15. Java中涉及到金钱计算方法
  16. matlab优化工具箱OptimizationToolbox使用方法
  17. 小红书笔记下沉怎么做到的?
  18. Windows下使用bat脚本批量创建文件夹
  19. linux怎么设置wifi密码,技术|怎样在 Arch Linux 终端上更改 WiFi 密码
  20. 分享个免费的货币汇率API

热门文章

  1. 亚马逊站点之土耳其站
  2. 阿里云申请免费CA认证
  3. 基于PaddleOCR和ToolGood.Words测试从图片中检测敏感词
  4. LabVIEW编的上位机控制汇川PLCH5U和汇川伺服运动 ,海康威视相机视觉对位,LabVIEW通过网口控制汇川H5U和Ethercat伺服
  5. 芝村乡如何从零开始学习理财
  6. matlab 风电叶片气动计算程序,基于Matlab与Solidworks方法的风力机叶片优化设计
  7. Linux(Ubuntu)使用setsid命令后台运行python代码并记录终端输出,并实现开机自启
  8. R语言使用dbern函数生成伯努利分布(0-1分布)密度数据、使用plot函数可视化伯努利分布密度数据( Bernoulli distribution)
  9. 三菱PLC的嵌入式软PLC技术的应用
  10. 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式国赛客观题以及详细题解