HashMap的哈希函数
下面的代码会发现一个特别之处,就是对key调用自带的native方法hashCode得到哈希值后没有直接返回,而是对哈希值向右无符号移动16位后与原始哈希值异或再返回。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里重点说两点,也是面试最常问的问题:
- 关于Object对象的hashCode函数
问:Java类对象中equals方法和hashCode方法关系?
答:《effective java》一书中有一条(第三版item11,第二版item9)专门讲到两者,有兴趣的可以认真阅读。这里简单说,如果重载equals函数必须重载hashCode,因为相等的(equals函数返回true)对象必须具有相等的哈希值(hashCode返回值一致)。如果相等的对象哈希值不同,其中一个直接的结果就是在放入hashMap的时候可能不会被放在同一个桶中,相同的对象放在不同的位置上显然是不能够被接受的。
- 为什么不直接返回hashCode函数结果
问:在HashMap中hash函数是如何实现的,为什么?
答:JDK源码这样实现是要避免因为低位的规律性导致的散列不均匀,混合原始哈希值的高位和低位来增加低位的随机性。至于效果如何,《An introduction to optimising a hashing strategy》这篇文章中有详细的论证和实验结果,这里不再赘述。
表的大小是2的幂次的好处
HashMap通过hash函数返回的哈希值对哈希表大小做取模运算得到当前key所在的位置。但是取模运算显然效率要低的多。为解决低效率的问题,使用高效的位运算代替取模运算,这样表的大小设置2的幂次,当计算模时用表的大小减1形成一个掩码,与key的哈希值做位与运算得到模。
例如:表的大小length=4(二进制表示为1000)
输入hash值:5(二进制表示为1001)
计算过程:(1000 - 1)& 1001 = 0001
结果:放入1位置处