从HashMap原码分析为什么使用2的幂次方作为数组长度

        在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之间,即保证了得到的索引不会产生数组越界异常。

        数据只是我随机选的,为了方便,你们也可以使用其他的数据进行验证一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值