HashMap中的hash函数
在写一个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 Mapprivate 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表值范围从-2147483648到2147483648。前后加起来大概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次的话,多了可能边际效用也不大,所谓为了效率考虑就改成一次了。
转载于:https://www.cnblogs.com/zhengwang/p/8136164.html
HashMap中的hash函数相关推荐
- HashMap中的hash与rehash
HashMap中的hash与rehash 我们知道HashMap中经常用到hash()方法. 比如:put()方法中 public V put(K key, V value){if(key==null ...
- HashMap中的Hash码怎么计算,为什么要这样做?
HashMap面试系列(1) Q:HashMap中的Hash码如何计算?为什么要这么做? 注意:HashCode()是对象的Hash码,和HashMap中的Hash码不是同一个东西 1.8版本如下 s ...
- Zcash中的hash函数
1. 引言 Zcash中的hash函数主要有: BLAKE2 Hash Function Group Hash into Jubjub Pedersen Hash Function Mixing Pe ...
- HashMap中的hash算法的几个思考
HashMap中哈希算法的关键代码 //重新计算哈希值 static final int hash(Object key) {int h;return (key == null) ? 0 : (h = ...
- hashmap中的hash扰动函数
https://www.zhihu.com/question/20733617 转载于:https://www.cnblogs.com/lushilin/p/6142597.html
- hash算法_阿里面试官:讲一下Hashmap中hash算法!
注:本文内容全部基于jdk8讲述. 相信很多人都知道,在JDK8中,HashMap的容量总是2的n次幂,那么这么设计的目的究竟是什么呢?我可不可以将默认的初始容量从16改成20呢,扩容的时候我可不可以 ...
- 全网把Map中的hash()分析的最透彻的文章,别无二家。
你知道HashMap中hash方法的具体实现吗?你知道HashTable.ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8 ...
- bat从数组中找出相同数字并删除_全网把Map中的hash()分析的最透彻的文章,别无二家...
原文地址:https://mp.weixin.qq.com/s/qCHkzs4JPOipB-ZzqrfbeQ 作者: Hollis 你知道HashMap中hash方法的具体实现吗? 你知道HashTa ...
- Hash函数及其应用
本文部分内容摘自网络,参考资料链接会在文后给出,在此感谢原作者的分享. 计算理论中,没有Hash函数的说法,只有单向函数的说法.所谓的单向函数,是一个复杂的定义,大家可以去看计算理论或者密码学方面的数 ...
最新文章
- linux普通高速缓存,linux页高速缓存
- Linux Shell 命令--tr
- python流程控制-Python流程控制
- syslog记录history历史记录
- 第四届泉水文化论坛协调会-商协社团:平台经济谋定水产业
- Python--粒子滤波定位案例程序
- 十六、PHP框架Laravel学习笔记——构造器的增删改
- 第六篇:如何学习C语言?
- 外观模式 门面模式 Facade 结构型 设计模式(十三)
- 面试官、女朋友都满意系列 - 决策树
- 解决Oracle11g密码180天过期,账号锁住的问题
- python optimize_SciPy优化optimize模块用法
- JAVAWeb汽车销售管理系统
- c++使用librdkafka kerberos认证
- 关于雷霄骅博士的博客FFMPEG+SDL的音频播放器播放有杂音的问题
- window10耳机插入没有声音,电脑里没有Realtek 高清晰音频管理器解决方法
- stata输出相关系数表到word
- Ueditor基本用法-kityformula-上传图片-手写公式myscript
- 强大的dex反编译器
- console接口是干嘛的(console接口是干嘛的电信室外光猫)
热门文章
- CSDDN特约专稿:个性化推荐技术漫谈
- 概述HTML文档的基本结构,HTML概述与基本结构
- e1载波把32个信道按_E1载波把32个信道按(11)方式复用在一条2.048Mb/s的高速信道上,每条话音信道的数据速率是(12)。...
- 如何计算_平整场地如何计算?
- pythonapriori算法特点_Python --深入浅出Apriori关联分析算法(一)
- python抓取简单网页_【Python3 爬虫】01_简单页面抓取
- java odbc dbf,什么是Java的最佳开源dbf驱动程序?
- python 分数序列求和公式_Python分数序列求和,编程练习题实例二十四
- wxpython多个面板_wxpython:隐藏其中一个拆分窗口面板
- python2 python3编码_Python2和Python3编码问题-从底层出发