受众群体
适合了解过HashMap一部分源码,但是不理解其中hash方法为什么那样做的原因。
源码解析
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这是1.8的版本,之前的版本步骤更多点,但是思路是一致的
h ^ (h >>> 16)
这段代码意思是把h的高16位数字和h的低16位数字异或。
为什么要怎么做呢?
由于Node<K,V>[]数组长度是默认是16,二进制下标是0000~1111,计算Key的数组下标方法是1111 & hash
,下标位置是取的hash值的低4位。如果单纯用Object.hashCode()
来计算hash值的话,即使两个hash值不一样,但是两者的低4位是一致的,就会造成了hash冲突。为了降低hash冲突,所以采用了低位异或高位的方式来打乱低位,目的是让高位参与hash值的计算。
这里注意下
0 ^ x = x
1 ^ x = ~x
,所以当hash值高位存在1的时候会把低位取反,当高位是0的时候,低位不变。如果高16位全是0,则hash值没有任何变化,这也是该算法想要的结果之一。
题外话:根据0 ^ x = x
1 ^ x = ~x
推断出0 ^ x ^ x = 0
1 ^ x ^ x = 1
任何数 异或两次相同的数还是等于原来的数。这也是用于对称加密的算法,比较简单,速度快。
PS:之前也会记这些概念,但是自己推算一次后印象非常深刻。
现在搞清了为什么要这样计算hash值,通过解析的过程中又出现了一个新问题:
计算数组下标的掩码每次都是用的都是 (16-1) (32-1) … 都是2的倍数-1,二进制就是1111…1111,也就是说要保证数组长度都是2的倍数,但是平时初始化数组长度的时候又是可以传非2的倍数,那是如何把非2倍数转成2的倍数的呢?
源码在此:
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;
}
这个就厉害了,先代入9试试。
- n = 9 - 1 = 8
1001 - 1 = 1000
- n |= n >>> 1
1000 | 0100 = 1100
- n |= n >>> 2
1100 | 0011 = 1111
- n |= n >>> 4
1111 | 0000 = 1111
- … 重复以上步骤
- 最后 + 1就等于2的N次方
该方法会把从高位开始第一个出现1之后所有的位都变成1
那为什么第一步要减一呢?我猜测:如果不减一,比如1000最后结果是1111+1=10000=16,这个结果翻了一倍,如果减一,算出来结果就是1000还是8。
总结
这种工匠精神是好的工程师都有的一种特性,我也正好有这种特性,但是工作久了后,慢慢地有点丢失了这种精神,以后我会多加注意,保持这种精神。