1.扰动函数
在hashmap中,put操作是这样进行的:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
其中会涉及到hash(key)
的运算,hash并不是直接使用hashCode(),而是这样:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里的操作就称之为扰动函数
根据取模操作中的&和(length-1)这篇文章中提到的,计算出hash后我们可以使用&进行取模操作来确定放置在哪里。
现在问题是:计算hash时,为什么不直接用hashCode(),而是
// 把哈希值右移16位,之后与原哈希值做异或运算。
(h = key.hashCode()) ^ (h >>> 16)
先说结论:
增大随机性,优化散列效果,让数据元素更加均匀的散列(减少碰撞)。
1.1 分析
1.1.1 Hash的取值范围
在Java中,hashCode()
方法返回的哈希值是一个32位的有符号整数(int
类型)。
因此,哈希值的取值范围是从 [-2147483648, 2147483647]
(即 -2^31
到 2^31 - 1
)。
1.1.2 扰动函数的计算过程
对于扰动hash的函数的计算,做一个拆分。
/**
* 扰动函数计算hash
*
* @param key .
* @return hash
*/
final int hashAndLog(Object key) {
int hashCode;
if (key == null) {
return 0;
}
hashCode = key.hashCode();
int rightShift = hashCode >>> 16;
int result = hashCode ^ rightShift;
log.info("int: {},[sourceHash] hashCode : {}", String.format("%-12s", hashCode), to2$Padding(hashCode, 32));
log.info("int: {},[rightShift] hashCode >>> 16 : {}", String.format("%-12s", rightShift), to2$Padding(rightShift, 32));
log.info("int: {},[result] hashCode ^ rightShift: {}", String.format("%-12s", result), to2$Padding(result, 32));
return result;
}
/**
* int值转为2进制显示
*
* @param number num
* @param padding 填充0 总长度
* @return Binary
*/
public static String to2$Padding(int number, int padding) {
String binaryString = Integer.toBinaryString(number);
return String.format("%" + padding + "s", binaryString).replace(' ', '0');
}
测试用例
@Test
void name4() {
String str = "abc";
int i = hashAndLog(str);
}
输出结果
int: 96354 ,[sourceHash] hashCode : 00000000000000010111100001100010
int: 1 ,[rightShift] hashCode >>> 16 : 00000000000000000000000000000001
int: 96355 ,[result] hashCode ^ rightShift: 00000000000000010111100001100011
上面的过程可以用下面的图来表示