Java-HashCode
0x01 摘要
HashCode是jdk里面大量采用的一个方法,经常用来区分对象。本文谈谈hashCode在不同类的实现。
0x02 Object
public native int hashCode();
在Object
中定义的hashCode方法是一个native
方法,他为不同Object返回了不同int型的hashCode。
Object.hashCode()
基本原则有三:
- 在一个java程序生命周期内,不论对同一个对象调用此方法多少次都必须返回相同的hashcode
Object.equals()
方法相同的对象,hashCode必须相同- java规范中并不要求
equals
方法不同的对象调用hashCode时就必须返回不同值,只不过设为不同值会提升性能,比如hashTable定位对象
0x03 Integer
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
可见,Integer类中是直接返回对象的int值。
0x04 String
/** Cache the hash code for the string */
private int hash; // hash初值为0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
注意:String类型的hash
初始值为0
代码中将String的value转换为char数组,然后循环进行以下操作:
h = 31 * h + val[i]
val[i]就是该char值的Ascii码。那么这里就相当于是从字符串的左往右遍历,将每个字符的Ascii码 31*h,最后得到一个叠加出来的hash
值。那么,如果字符串长度为0 则hashCode也会是0 。
这里选择31
作为乘数的原因是因为3点:
- 31是较小的奇质数,使得结果不会太小导致冲突率高也不容易太大导致溢出
31 * i
可以做这个等值优化:31h = 32*h - h = h<<5 - 1
,这是移位运算速度极快- 如果选择偶数,乘2相当于移位运算可能导致溢出,信息丢失
但要注意,jdk并不能保证不同String的hashCode不同,比如字符串"gdejicbegh"与字符串"hgebcijedg"具有相同的hashCode()返回值-801038016。
0x05 HashMap
0x06 WeakHashMap
final int hash(Object k) {
int h = k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
这个算法和Jdk1.7
中的HashMap
相同,主要是尽量让所有k.hashCode()
得到的bit
的每一位变动都能体现在hash
结果上(这种情况下只有原hashCode第3位高1位变化不会反应到结果中),让分布更均匀。最终使得落在HashMap
的每个bucket
中的元素数量更均匀,减少hash冲突。
0xFF 参考文档
科普:为什么 String hashCode 方法选择数字31作为乘子
Why does Java’s hashCode() in String use 31 as a multiplier?