1. 前言

HashMap对于Java程序员来说一定不陌生,除了平时开发会经常使用外,它也是面试官非常喜欢问的一个知识点。HashMap是哈希表的一个经典实现,底层数据结构是数组+链表,在JDK8中还引入了红黑树,以解决链表线性查找的效率问题。HashMap设计的非常优秀,源码两千多行,有很多可以拿出来讨论的点,本篇文章主要分析HashMap二次哈希的目的。

2. 哈希码的作用

首先,我们得先了解哈希码的作用是什么?HashMap底层采用数组+链表/红黑树的数据结构来存储键值对的映射关系,数组就是若干个哈希槽Solt,HashMap会通过Key算出的哈希码来计算下标Index,Index决定了键值对应该落在哪个槽里。不同的哈希码算出相同的下标Index,就会导致哈希碰撞,一旦发生哈希碰撞,HashMap的查找效率就会从O(1)退化成O(n)或者O(logn)。所以,一个好的哈希函数应该要尽可能的分散,否则就会影响到HashMap的效率。

3. 二次Hash

我们已经知道,HashMap会根据哈希码计算下标,哈希码的分散性越好,HashMap的效率也就越高。我们先看一下HashMap计算下标的过程,就知道它为啥要做二次Hash了。

static int indexFor(int h, int length) {return h & (length-1);
}

上面是HashMap根据二次Hash计算出的哈希码,计算键值对下标的代码,length是底层数组的长度。HashMap采用了位运算,而非我们常见的取模运算,这里你可以先略过,它俩的效果是一样的。

我们先来看看如果不做二次Hash的情况下,会出现什么问题。现在,我假设数组长度为16,那么当哈希码为5时,下标Index结果是5。

 00000000000000000000000000000101
&00000000000000000000000000001111
=00000000000000000000000000000101
=5

当哈希码为65541时,下标Index结果依然是5,不同的哈希码算出相同的下标,哈希碰撞了。

 00000000000000010000000000000101
&00000000000000000000000000001111
=00000000000000000000000000001101
=5

从这个与运算的过程,大家肯定也都发现了,就是哈希码的高位压根就没有参与运算,全部被丢弃了。不管哈希码的高位是多少,都不会影响最终Index的计算结果,因为只有低位才参与了运算,这样的哈希函数我们认为是不好的,它会带来更多的冲突,影响HashMap的效率。

如何解决这个问题呢?最简单的办法就是让高位也参与到运算,高位不一样也会导致最终的Index结果不一样,减少哈希碰撞的概率。事实上,HashMap也就是这么做的,下面是HashMap做二次Hash的源码:

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

HashMap通过将哈希码的高16位与低16位进行异或运算,得到一个新的哈希码,这样就可以让高位也参与到运算,这个函数也被称作「扰动函数」。

我们用同样的哈希码,来看看经过二次Hash后的哈希码,是否会带来不一样的效果。
仍然假设数组长度为16,那么当哈希码为5时,下标Index是5,结果不变。

 0000000000000101
^0000000000000000
=000000000000010100000000000000000000000000000101
&00000000000000000000000000001111
=00000000000000000000000000000101
=5

当哈希码为65541时,下标Index结果是4,竟然没有发生哈希碰撞。

 0000000000000101
^0000000000000001
=000000000000010000000000000000010000000000000100
&00000000000000000000000000001111
=00000000000000000000000000000100
=4

可以看到,HashMap通过加入一个扰动函数,让原本会发生碰撞的两个哈希码,不再冲突。

4. 为啥右移16位

HashMap的扰动函数,是拿高16位和低16位做异或运算,把高位的特征和地位的特征组合起来,以此来降低哈希碰撞的概率。为啥是16位?而不是8位或24位或其它位?

根据哈希码计算下标Index的过程,大家也发现了。实际上,只有数组长度以内的低位才会参与运算。例如数组长度是16,那么只有低4位会参与计算;如果数组长度是256,那么只有低8位会参与计算;如果数组长度是65536,那么只有低16位会参与计算。HashMap取16位是一个折中的数字,绝大部分情况下,HashMap数组的长度都不会超过65536。

5. 总结

HashMap底层采用数组+链表/红黑树来存储键值对,会根据Key的哈希码来计算键值对落在数组的哪个下标。如果不同的哈希码算出相同的下标,就会导致哈希碰撞,影响HashMap的性能。HashMap要做的,就是尽量避免哈希碰撞,所以加入了扰动函数。扰动函数会将哈希码的高16位与低16位做异或运算,让高位也参与到下标的计算过程中来,从而影响最终下标的计算结果,减少哈希碰撞的概率。至于为啥是16位,这是因为哪些位会参与到下标的计算,取决于HashMap数组的长度,在绝大部分情况下,数组的长度都不会超过65536,16位是一个折中的数字。

HashMap为啥要二次Hash相关推荐

  1. BZOJ2351[BeiJing2011]Matrix——二维hash

    题目描述 给定一个M行N列的01矩阵,以及Q个A行B列的01矩阵,你需要求出这Q个矩阵哪些在原矩阵中出现过. 所谓01矩阵,就是矩阵中所有元素不是0就是1. 输入 输入文件的第一行为M.N.A.B,参 ...

  2. NEUQ 2015: Bitmap(二维hash)

    题目链接 题意 给一个N×NN × NN×N的矩阵问包含多少个M×MM×MM×M的子矩阵,子矩阵不一定完全相同,同时加上某个数相同也算 思路 首先差分,这样就可以直接找匹配的矩阵. 二维hash+容斥 ...

  3. BZOJ 1567: [JSOI2008]Blue Mary的战役地图 矩阵二维hash

    1567: [JSOI2008]Blue Mary的战役地图 Description Blue Mary最近迷上了玩Starcraft(星际争霸) 的RPG游戏.她正在设法寻找更多的战役地图以进一步提 ...

  4. bzoj 2351: [BeiJing2011]Matrix(二维Hash)

    2351: [BeiJing2011]Matrix Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 938  Solved: 303 [Submit] ...

  5. HashMap和Hashtable中的hash值是怎么计算的

    上一篇讲了String.Integer复写了Object中的hashCode方法,而对于HashMap或类对象来说是直接使用了Object中的hashCode方法.正文如下: public class ...

  6. BZOJ1567 [JSOI2008]Blue Mary的战役地图(二分+二维hash)

    题意 问边长为n的两个正方形中最大的相等子正方形.(n<=50) 题解 用到了二维hash,感觉和一维的不太一样. 对于列行有两个不同的进制数然后也是通过类似前缀和的方法差分出一个矩形的hash ...

  7. Java 集合深入理解 (十一) :HashMap之实现原理及hash碰撞

    文章目录 前言 哈希表原理 实现示例 HashMap实现原理 全篇注释分析 实现注意事项 默认属性分析 属性分析 构造方法分析 重要的put方法 总结 前言 哈希表(hashMap)又叫散列表 是一种 ...

  8. HashMap的实现原理及hash冲突(碰撞)解决方法

    HashMap 采用一种所谓的"Hash 算法"来决定每个元素的存储位置.当程序执行 map.put(String,Obect)方法 时,系统将调用String的 hashCode ...

  9. HashMap为啥初始化大小是16

    HashMap的默认初始化长度是多少? static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 在JDK1.8的 235 ...

最新文章

  1. PCA、碎石图、PCA+正确的维度个数、增量PCA(IncrementalPCA)、随机PCA(Randomized PCA)、KernelPCA
  2. Skype for Business Server 2015-升级-实战公开课(免费视频)
  3. 概念辨析:工厂模式 工厂方法模式 简单工厂模式 抽象工厂
  4. 前端学习(1731):前端系列javascript之发布窗口布局下
  5. Vue监视---vue工作笔记0005
  6. Git 基础(十)—— 常用命令
  7. python中的common_common:个人基础函数库
  8. 输入法相关的使用(跳转)
  9. fiddler的基本使用教程
  10. Mp3原理及文件格式解析
  11. 背包问题1:【SSL】1059.01背包问题——2021-03-10更
  12. pytorch实现NS方程求解-基础PINN
  13. 新增Tao插件,Red Giant 经典特效插件 Trapcode Suite 13 for Win/Mac
  14. Docker加速器 DaoCloud
  15. HP工作站如何在BIOS下开启、关闭安全芯片
  16. 直播弹幕系统(三)- 直播在线人数统计
  17. 国考省考结构化面试:组织管理题,调研题,宣传题,活动题,整治题
  18. 推荐 9 个免费图片网站,我的存货都在这了
  19. VB 操作Excel
  20. DIV+CSS实现旅游网站首页

热门文章

  1. C++指针地址和指针的值
  2. 树莓派研究笔记(6)-- Lakka模拟游戏机
  3. 掌控板结合Arduino实现数据上传阿里云
  4. 搜索输入框下拉列表热词搜索的实现
  5. php使用pg中copy命令,PGSQL COPY命令导入/导出数据
  6. python tracer(false)_Python龟太空入侵者子弹不打入侵者
  7. php json decode 多维,PHP Json_decode多维数组
  8. 软件测试OA办公自动化系统测试方案
  9. 网景:曾经让微软感受到威胁的公司
  10. 以文件读取和写入的方式实现病毒和人DNA的匹配(采用KMP算法)