在写一个HashSet时候有个需求,是判断HashSet中是否已经存在对象,存在则取出,不存在则add添加。HashSet也是通过HashMap实现,只用了HashMap的key,value都存储一个赘余的Object,如下是HashSet中持有的HashMap对象,add函数:

  private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
 
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

中在HashMap中的hash函数判断key是否存在,如下图所示:

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

在看到这段代码时疑问产生了,为什么hash函数这么设计?查过资料之后解释如下(如下内容来自网络-知乎胖胖的答案):

这段代码叫“扰动函数”

大家都知道上面代码里的key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。

理论上散列值是一个int型,如果直接拿散列值作为下标访问HashMap主数组的话,考虑到2进制32位带符号的int表值范围从-21474836482147483648。前后加起来大概40亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。

但问题是一个40亿长度的数组,内存是放不下的。你想,HashMap扩容之前的数组初始大小才16。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来访问数组下标。源码中模运算是在这个indexFor( )函数里完成的。

bucketIndex = indexFor(hash, table.length);

indexFor的代码也很简单,就是把散列值和数组长度做一个"与"操作,

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

顺便说一下,这也正好解释了为什么HashMap的数组长度要取2的整数幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。

    10100101 11000100 00100101
& 00000000 00000000 00001111
----------------------------------
00000000 00000000 00000101 //高位全部归零,只保留末四位

但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,就无比蛋疼。

时候“扰动函数”的价值就体现出来了,说到这里大家应该猜出来了。看下面这个图,

右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

最后我们来看一下Peter Lawley的一篇专栏文章《An introduction to optimising a hashing strategy》里的的一个实验:他随机选取了352个字符串,在他们散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标。

结果显示,当HashMap数组长度为512的时候,也就是用掩码取低9位的时候,在没有扰动函数的情况下,发生了103次碰撞,接近30%。而在使用了扰动函数之后只有92次碰撞。碰撞减少了将近10%。看来扰动函数确实还是有功效的。

但明显Java 8觉得扰动做一次就够了,做4次的话,多了可能边际效用也不大,所谓为了效率考虑就改成一次了。

HashMap中的hash函数的更多相关文章

  1. hashCode及HashMap中的hash()函数

    一.hashcode是什么 要理解hashcode首先要理解hash表这个概念 1. 哈希表 hash表也称散列表(Hash table),是根据关键码值(Key value)而直接进行访问的数据结构 ...

  2. [ 转载 ]hashCode及HashMap中的hash()函数

    hashCode及HashMap中的hash()函数   一.hashcode是什么 要理解hashcode首先要理解hash表这个概念 1. 哈希表 hash表也称散列表(Hash table),是 ...

  3. HashMap 中的 hash 函数

    1. 什么是 hash 函数 hash 函数,即散列函数,或叫哈希函数.它可以将不定长的输入,通过散列算法转换成一个定长的输出,这个输出就是散列值.需要注意的是,不同的输入通过散列函数,也可能会得到同 ...

  4. HashMap中的hash算法总结

    前言 算法一直是我的弱项,然而面试中基本是必考的项目,刚好上次看到一个HashMap的面试题,今天也来学习下 HashMap中的hash算法是如何实现的. 数学知识回顾 << : 左移运算 ...

  5. 深入理解HashMap(及hash函数的真正巧妙之处)

    原文地址:http://www.iteye.com/topic/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多 ...

  6. HashMap中的hash算法中的几个疑问

    HashMap中哈希算法的关键代码 //重新计算哈希值 static final int hash(Object key) { int h; return (key == null) ? 0 : (h ...

  7. 【Java深入研究】11、深入研究hashmap中的hash算法

    一.简介 大家都知道,HashMap中定位到桶的位置 是根据Key的hash值与数组的长度取模来计算的. JDK8中的hash 算法: static final int hash(Object key ...

  8. K:HashMap中hash函数的作用

      在分析了hashCode方法和equals方法之后,我们对hashCode方法和equals方法的相关作用有了大致的了解.在通过查看HashMap类的相关源码的时候,发现其中存在一个int has ...

  9. 【转】【java源码分析】Map中的hash算法分析

    全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...

随机推荐

  1. Hive的安装与配置

    1.因为我使用MySQL做为Hive的元数据库,所以先安装MySQL. 参考:http://www.cnblogs.com/hunttown/p/5452205.html 登录命令:mysql -h主 ...

  2. http协议基础(三)几种数据传输方式

    说说http协议的一些特点: 1)无状态 http协议是一种自身不对请求和响应之间的通信状态进行保存的协议,即无状态协议. 这种设置的好处是:更快的处理更多的请求事务,确保协议的可伸缩性 不过随着we ...

  3. 003-ubuntu上安装mysql

    安装如下: 1.安装服务端:# sudo apt-get  install mysql-server. 2.安装客户端:# sudo apt-get -y install mysql-server. ...

  4. keras load model 遇到 自定义函数 Lambda(lambda x: softmax(x, axis=1), NameError: global name 'softmax' is not defined

    问题 在定义模型的时候,自定义了一个函数 模型保存之后,load 模型的时候报错: 解决 load 模型的时候需要指定custom object 参考: https://faroit.github.i ...

  5. Python的问题解决: IOError: [Errno 32] Broken pipe

    被该问题困扰的人还是挺多的,所以又对这个问题进行了一些更深入的分析,希望可以解决读者的问题新版本:Python 的 Broken Pipe 错误问题分析 遇到一个很奇怪的问题, web.py代码里面报 ...

  6. SpringBoot集成Socket服务后打包(war包)启动时如何启动Socket服务(web应用外部tomcat启动)

      1.首先知道SpringBoot打包为jar和war包是不一样的(只讨论SpringBoot环境下web应用打包)     1.1.jar和war包的打开方式不一样,虽然都依赖java环境,但是j ...

  7. go环境搭建—基于CentOS6.8

    1. 背景 在当前的中国网络环境下,我们无法访问Google的服务的,包括Golang.org.从第三方网站下载预编译的二进制Go发行版可能存在第三方源代码注入的风险,例如之前的XcodeGhost. ...

  8. Python 自学基础(四)——time模块,random模块,sys模块,os模块,loggin模块,json模块,hashlib模块,configparser模块,pickle模块,正则

    时间模块 import time print(time.time()) # 当前时间戳 # time.sleep(1) # 时间延迟1秒 print(time.clock()) # CPU执行时间 p ...

  9. 20145105 《Java程序设计》实验二总结

    实验二 Java面向对象程序设计 一. 实验内容: 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.多态.建模 初步掌握UML 熟悉S.O.L.I.D原则 了解设计模式 二. 实验步骤 (一 ...

  10. 垒骰子|2015年蓝桥杯B组题解析第九题-fishers

    垒骰子 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰子:1 的 ...