在hashmap底层中我们经常看到计算数据的时候,都是进行位运算,比如在计算hashcode值的时候,会进行很多次的移位运算,如下为jdk1.7计算hash值的原码,就是进行移位操作,那么为什么要进行这些操作呢?这是为了减少hash碰撞(hash冲突),多次的移位运算,使得key的高位和低位都能够参与运算,计算更加的充分,使得得到的hashcode值唯一(不可能),虽然这样还是会有冲突存在,但是也会减少发生的概率。
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
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);
}
那么为什么数组长度总是二的幂次方呢?扩容的时候会调用这个方法。他会判断原来的数组大小是否大于最大值,然后再判断是否大于1,成立就会调用Integer的方法进行操作。
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
//扩容调用的方法
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//下面方法是Integer里的方法
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
i 1011 0010 //
>> 1
i 0101 1001
| //或操作
i 1111 1011
>>2
0011 1110
|
i 1111 1111
>>4 //下面不管移位多少都是1111 1111
i 1111 1111
>>1 0111 1111
-
1000 0000 //右移一位后相减的到1000 0000
经过Integer的方法得到1000 0000,返回的到的数据是128,传入的值是随机的,通过计算后得到的这个值,传入的是178,那么小于的于178的二的幂次方数就是128,而这个178是通过减一之后左移一位得到的,那么原来的值是90,所以大于等于90的2的幂次方就是128,即得到了数组的容量大小。
为什么要先进行减一过后左移一位扩大两倍呢?因为roundUpToPowerOf2方法要得到一个大于等于传入值的2的幂次方数。为什么要减一呢?那么我们试一下加一之后的计算
i 1011 0110
>>1 0101 1011
| 1110 1101
>>2 0011 1011
| 1101 0110
>>4 0000 1101
| 1101 1011
>>8 //后面怎么计算都是这个数1101 1011
计算得到这个数1101 1011为273,得到一个奇数,那么这个数不为2的幂次方数,不符合我们的要求的数。
i 1011 0100
>>1 0101 1010
| 1110 1110
>>2 0011 1011
| 1101 0101
>>4 0000 1101
| 1101 1000
>>8 //后面怎么计算都只能得到这个数1101 1000
计算得到这个数1101 1000 为216,那么这个数不为2的幂次方数,不符合我们的要求的数。通过验证,那么只有减一的时候才会符合我们需要的2的幂次方数,这也是为什么数组长度是2 的幂次方数。那么为什么要扩大两倍呢?因为需要的是大于等于传入值的2的幂次方数,那么反正都要找大于他的数,那么我们先扩大不就直接得到需要的数了吗。不扩的,那么得到的是小于等于他的2的幂次方数,那么扩大不就得到大于等于2的幂次方数了吗。
其实使用2的幂次方数作为他的数组大小,还有一个因素,在hashmap中计算对象在数组中的位置的时候,我们使用的是hash & length-1 来计算的,那么我们来验证一下
//假如数组大小是16 hash值是 231398238
hash 1101 1100 1010 1101 1011 0101 1110
length-1 0000 0000 0000 0000 0000 0000 1111
&
index 0000 0000 0000 0000 0000 0000 1110 //14
经过计算得到对象在数组中的index,为14在0~15之间,即保证了得到的索引不会产生数组越界异常。
数据只是我随机选的,为了方便,你们也可以使用其他的数据进行验证一下