为什么要有HashMap的hash()方法,难道不能直接使用KV中K原有的hash值吗?在HashMap的put、get操作时为什么不能直接使用K中原有的hash值。

    /*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower.  Because the table uses power-of-two masking, sets of * hashes that vary only in bits above the current mask will * always collide. (Among known examples are sets of Float keys * holding consecutive whole numbers in small tables.) So we * apply a transform that spreads the impact of higher bits * downward. There is a tradeoff between speed, utility, and * quality of bit-spreading. Because many common sets of hashes * are already reasonably distributed (so don't benefit from * spreading), and because we use trees to handle large sets of * collisions in bins, we just XOR some shifted bits in the * cheapest possible way to reduce systematic lossage, as well as * to incorporate impact of the highest bits that would otherwise * never be used in index calculations because of table bounds. */ static final int hash(Object key) { int h;  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

从上面的代码可以看到key的hash值的计算方法。key的hash值高16位不变,低16位与高16位异或作为key的最终hash值。(h >>> 16,表示无符号右移16位,高位补0,任何数跟0异或都是其本身,因此key的hash值高16位不变。) 
 
为什么要这么干呢? 
这个与HashMap中table下标的计算有关。

n = table.length;
index = (n-1) & hash;

因为,table的长度都是2的幂,因此index仅与hash值的低n位有关,hash值的高位都被与操作置为0了。 
假设table.length=2^4=16。 
 
由上图可以看到,只有hash值的低4位参与了运算。 
这样做很容易产生碰撞。设计者权衡了speed, utility, and quality,将高16位与低16位异或来减少这种影响。设计者考虑到现在的hashCode分布的已经很不错了,而且当发生较大碰撞时也用树形存储降低了冲突。仅仅异或一下,既减少了系统的开销,也不会造成的因为高位没有参与下标的计算(table长度比较小时),从而引起的碰撞。

HashMap#tableSizeFor()

源码:

    static final int MAXIMUM_CAPACITY = 1 << 30; /** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }

这个方法被调用的地方:

    public HashMap(int initialCapacity, float loadFactor) {/**省略此处代码**/ this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }

由此可以看到,当在实例化HashMap实例时,如果给定了initialCapacity,由于HashMap的capacity都是2的幂,因此这个方法用于找到大于等于initialCapacity的最小的2的幂(initialCapacity如果就是2的幂,则返回的还是这个数)。 
下面分析这个算法: 
首先,为什么要对cap做减1操作。int n = cap - 1; 
这是为了防止,cap已经是2的幂。如果cap已经是2的幂, 又没有执行这个减1操作,则执行完后面的几条无符号右移操作之后,返回的capacity将是这个cap的2倍。如果不懂,要看完后面的几个无符号右移之后再回来看看。 
下面看看这几个无符号右移操作: 
如果n这时为0了(经过了cap-1之后),则经过后面的几次无符号右移依然是0,最后返回的capacity是1(最后有个n+1的操作)。 
这里只讨论n不等于0的情况。 
第一次右移

n |= n >>> 1;

由于n不等于0,则n的二进制表示中总会有一bit为1,这时考虑最高位的1。通过无符号右移1位,则将最高位的1右移了1位,再做或操作,使得n的二进制表示中与最高位的1紧邻的右边一位也为1,如000011xxxxxx。 
第二次右移

n |= n >>> 2;

注意,这个n已经经过了n |= n >>> 1; 操作。假设此时n为000011xxxxxx ,则n无符号右移两位,会将最高位两个连续的1右移两位,然后再与原来的n做或操作,这样n的二进制表示的高位中会有4个连续的1。如00001111xxxxxx 。 
第三次右移

n |= n >>> 4;

这次把已经有的高位中的连续的4个1,右移4位,再做或操作,这样n的二进制表示的高位中会有8个连续的1。如00001111 1111xxxxxx 。 
以此类推 
注意,容量最大也就是32bit的正数,因此最后n |= n >>> 16; ,最多也就32个1,但是这时已经大于了MAXIMUM_CAPACITY ,所以取值到MAXIMUM_CAPACITY 。 
举一个例子说明下吧。 

这个算法着实牛逼啊!

注意,得到的这个capacity却被赋值给了threshold。

this.threshold = tableSizeFor(initialCapacity);

开始以为这个是个Bug,感觉应该这么写:

this.threshold = tableSizeFor(initialCapacity) * this.loadFactor;

这样才符合threshold的意思(当HashMap的size到达threshold这个阈值时会扩容)。 
但是,请注意,在构造方法中,并没有对table这个成员变量进行初始化,table的初始化被推迟到了put方法中,在put方法中会对threshold重新计算,put方法的具体实现请看这篇博文。

参考资料

1.Java HashMap工作原理及实现 
2.Java7的HashMap初始化变化 
3.java HashMap

转载于:https://www.cnblogs.com/liujinhong/p/6576543.html

HashMap的hash()相关推荐

  1. 调试JDK源码-一步一步看HashMap怎么Hash和扩容

    调试JDK源码-一步一步看HashMap怎么Hash和扩容 调试JDK源码-ConcurrentHashMap实现原理 调试JDK源码-HashSet实现原理 调试JDK源码-调试JDK源码-Hash ...

  2. HashMap解决hash冲突的方法

    HashMap解决hash冲突的方法 博客分类: jvm虚拟机 在Java编程语言中,最基本的结构就是两种,一种是数组,一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造,HashMap ...

  3. HashMap的hash冲突解决方案

    Hash函数 非哈希表的特点:关键字在表中的位置和它之间不存在一个确定的关系,查找的过程为给定值一次和各个关键字进行比较,查找的效率取决于和给定值进行比较的次数. 哈希表的特点:关键字在表中位置和它之 ...

  4. HashMap中hash(Object key)原理(hashcode >>> 16)

    大家都知道(jdk1.8)HashMap中计算数组下标是HashMap的核心算法.小编今天在看HashMap源码中看到了hash(Object key)方法百思不得其解.小编问百度,查找相关博客,甚至 ...

  5. hash算法_阿里面试官:讲一下Hashmap中hash算法!

    注:本文内容全部基于jdk8讲述. 相信很多人都知道,在JDK8中,HashMap的容量总是2的n次幂,那么这么设计的目的究竟是什么呢?我可不可以将默认的初始容量从16改成20呢,扩容的时候我可不可以 ...

  6. HashMap中hash(Object key)原理,为什么(hashcode >>> 16)。

    未经本人容许,禁止转载 大家都知道(jdk1.8)HashMap中计算数组下标是HashMap的核心算法.小编今天在看HashMap源码中看到了hash(Object key)方法百思不得其解.小编问 ...

  7. java hashmap hash算法,jdk1.8 中 HashMap 的 hash 算法和数组寻址

    开篇 本文基于 jdk1.8 讲述 HashMap 的 hash 算法,但是不会详细介绍其他相关内容(比如用法,底层数据结构).所以必须事先知晓下面几点: HashMap 的底层数据结构是数组,在数组 ...

  8. java基础篇 - HashMap 理解Hash碰撞

    HashMap是大家都在用,面试的时候也经常会被考的考点,在这篇文章中说下HashMap的hash碰撞和减轻碰撞的优化. 1.什么是hash碰撞 在解释Hash碰撞之前先说一下hashmap的存储结构 ...

  9. 蔚来一面:HashMap 的 hash 方法原理是什么?看完这篇还不懂HashMap的hash原理,那我要哭了~

    Warning:这是<Java 程序员进阶之路>专栏的第 55 篇.那天,小二去蔚来面试,面试官老王一上来就问他:HashMap 的 hash 方法的原理是什么?当时就把裸面的小二给蚌埠住 ...

最新文章

  1. asp.net窗体操作总结
  2. python实现顺序查找和哈希查找
  3. PreparedStatement和Statement比较
  4. eclipse报告Plugin execution not covered by lifecycle configuration
  5. [Flags]标识的Enum不能使用Html.GetEnumSelectList方法
  6. 这款耳机堪比千元级的AirPods
  7. 饿了么超时20分钟_饿了么回应“多等5分钟”,网友气炸了
  8. 面试官:说说你知道多少种线程池拒绝策略
  9. Liunx上训练模型的常见情况(不定期更新)
  10. 亮屏变“黄”,暗屏变“绿”,iPhone 12用户太难了
  11. css3 menu 手机菜单3
  12. 如果看了此文,你还不懂傅里叶变换,那就过来掐死我吧(完整版)
  13. paip.ecshop邮件模板修改一个密码找回 一个留言回复
  14. 30款前端特效源码分享
  15. chrome浏览器无法登录印象笔记
  16. IOS通过加速感应器实现手机实现手机屏幕上的足球可以来回的滚动反弹
  17. O'Reilly Java系列书籍建议阅读顺序(转自蔡学庸老师)
  18. Error starting ApplicationContext.
  19. 室内定位系列(一)——WiFi位置指纹(译)
  20. Python轻松实现AI换脸

热门文章

  1. NLP《Tranformer和Self-Attention》
  2. 谷歌浏览器设置定位_Chrome如何设置允许所有网站跟踪我的地理位置
  3. 去哪儿-01-EnvironmentalPre
  4. 面向消费者的自动文本分析(Automated Text Analysis for Consumer Research) 2017 JCR 论文阅读
  5. 事业单位考试考试【转载】
  6. hp服务器增加raid卡,惠普ProLiant服务器Raid卡配置过程详解
  7. 爬虫学习二: bs4 xpath re
  8. Spring IOC注解开发
  9. Struts使用细节
  10. 信号与槽是如何实现的_苹果iPhone 12信号仍弱?网友反馈打不进电话需重启解决...