在java中,Map.hashCode()函数是在具有一定工作积累后,为了更好的成长不可避免需要研究的内容。
首先,我们先看下原始代码:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public native int hashCode();
一、hashCode()函数
我们可以看到hashCode()函数是一个 native标记的函数,因此我们目前只可以对照上方的注释来对该函数进行一定的说明。
总结如下:
- 该方法会返回一组hash值,目的是更好的满足诸如HashMap散列图表所需的散列算法值(并不是说对于普通对象没有用);
- 在程序执行时,在不改变现有对象及内部的情况下保证每次执行获得一个相同的integer值(注意,是一个int值,32位);但不保证在两次执行时,相同情况创建的对象code相等;
- 在equals()函数相等的两个对象,他们的hashCode值一直相等;
- 在equals()函数不相等的两个对象,他们的hashCode值不一定一直相等(可以理解为:为了保证不同对象hashCode必定不相等,并不是明智的,必定会损耗一定的系统资源和效率);
- 对于一个特定的对象他的hashCode值差不多是特别的(可以理解为通过转换对象在内存中的地址获取到的hashCode,并且这个值得获取不需要java技术实现(因此是native));
如果不太清楚,请多阅读几遍这些总结,对hashCode()函数来说,这些也真的是他的核心了。
二、hash(Object key)函数
对与hash()函数,相对会简单理解很多。
总结如下:
- (h = key.hashCode()) ^ (h >>> 16) 对这行代码理解为:为了使hash后的值更加的散列,使高16位bit(int的长度是32位)和低16位bit进行,异或运算,使低16位同时具有高位和低位的特点,这样的目的是,在极端情况下,高位可能并不参与任何的哈希计算(比如在一个小的table中 使用连续的float数值作为key,在计算时,碰撞是经常发生的。如果只使用hashCode作为hash,对于高位来说,起到的作用也是有限的);
- 要明白之所以使用这样的方式计算,也是从速度,效率,实用性这些综合考量的结果;使用简单的异或计算可以减少系统的损耗
三、补充
虽然HashMap使用拉链法作为hash碰撞的解决方案,但是仍可以在hash函数的设置上去进行一定程度的优化,来减少碰撞的可能性。
- 如果直接使用
key.hashCode()
作为hash值的话,存在一些问题。
举例说明,HashMap的默认长度为16,并且是通过(table.length - 1) & hash
的方式得到key在table中的下标
如果key1.hashCode()
=1661580827(二进制为0110,0011,0000,100
1,1011,0110,0001,1011),key2.hashCode()
=1661711899(二进制为0110,0011,0000,101
1,1011,0110,0001,1011)
在与掩码进行与的过程中,只有后4位起作用,导致得到的下标值均为11,导致高位完全失效,加大了冲突的可能性。- 如果通过高位向低位异或传播的话,高位同样参与到key在table中下标的运算,减少了碰撞的可能性
key1.hashCode() ^ (key1.hashCode() >>>16)
=1661588754(二进制为0110,0011,0000,1001,1101,0101,0001,0010)key2.hashCode() ^ (key2.hashCode() >>>16)
=1661719824(二进制为0110,0011,0000,1011,1101,0101,0001,0000)
在于掩码进行与操作得到的下标分别为2和0,减少了冲突的可能性。
(文章如存在任何问题,欢迎指正,共同进步)