HashMap中roundUpToPowerOf2代码作用及原理

先上源代码

 private static int roundUpToPowerOf2(int number) {

        int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY
         : (rounded = Integer.highestOneBit(number)) != 0 ? 
         (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                                        : 1;
        return rounded ;
    }

过于抽象,换成通俗一点的

         if (number >= MAXIMUM_CAPACITY){
             rounded = MAXIMUM_CAPACITY;
         }else{
             if ((rounded = Integer.highestOneBit(number)) != 0){
                 if (Integer.bitCount(number) > 1){
                     rounded = rounded << 1;
                 }else {
                     rounded = rounded;
                 }
             }else {
                 rounded = 1;
             }
         }

当输入的 number 超过最大值,直接掰回来,将大小设置为最大值;如果 number 小于最大值,则进行一个对 number 取最高位为 1,其余位为 0 的骚操作。直接看 Integer 内的 highestOneBit 方法

    public static int highestOneBit(int i) {
        i |= (i >>  1);
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }

这个方法依旧很抽象,首先明白几个知识点

1、补码: 对于正数而言,补码即为原码;  例如:17 = 【10001】

 2、  >>: 将i转换成二进制后将该数往右平移;

 3、     |:  按位取或,输入有1时则为1,否则为0; 例如:10001 | 00100 = 10101

         int i = 17;
         Integer.toBinaryString(i);         // 10001
         Integer.toBinaryString(i >> 1);    // 01000
         Integer.toBinaryString(i | i >> 1);// 11001

了解了基本规则后便可发现,该方法先对 i 往右平移 1 位,同时与原值进行按位取或,如同上边代码中的注释,保证最高位的下一位也为 1,从而得到一个最高位开始连续两个数均为 1 的值

然后进行下一步,由于最高位为连续的两个 1,因此对该值进行往右平移 2 位,并按位取或,此时可以发现,已经获得了一个最高位连续为 1 的值

         Integer.toBinaryString(i);         // 11001
         Integer.toBinaryString(i >> 2);    // 00110
         Integer.toBinaryString(i | i>> 1); // 11111

当经过了几轮这样的右移操作,就能得到一个从最高位开始全部为 1 的二进制数,对其进行无符号右移一位( >>>  忽略符号位右移,同时空缺位补0)

         Integer.toBinaryString(i);            // 11111
         Integer.toBinaryString(i >>> 2);      // 01111
         Integer.toBinaryString(i - i >>> 2);  // 10000

至此,得到了一个小于或等于  i  的 2^N 次方的数,通过观察发现,当输入的 number 为 0 时后,highestOneBit 方法会返回一个 0,此时 roundUpToPowerOf2 将会返回 1,

当输入的number满足了【 highestOneBit(number) ! = 0 】的要求后,使用bitCount方法对number的二进制形式的 1 的数量进行判断,当 bitCount 返回的值等于 1 时,代表 number 即为 2^N 次方的数,否则 number  >  rounder,通过左移一位使得改值翻倍,得到一个足够容下 number 的大小并且大小为 2^N 次方的空间。

JAVA8里面这段代码改的更精简了

    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;
    }

首先对cap - 1,然后进行一系列位移运算,得到的是一个大于等于cap - 1并且最高位连续为1的值,通过+1便得到一个2^N,同时能保证这个2^N大于或等于cap。

 

知道如何生成一个 2^N 之后,就可以研究一下 HashMap 为何必须使用 2^N 作为 HashMap 的容量大小了。

jdk1.7中的 HashMap 是由数组加链表组成,每次 put 的时候都要对 key 值做一个极其烧脑的 hash 操作,得到一个 hash 值

// 整一个足够均匀的hash值
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);
}


// 获得下标
static int indexFor(int hash, int length) {
    return hash & (length-1);
}

大部分情况下这个 hash 值基本不会重复,hash值需要放入对应的数组下标,hash值为一个int,但并不能保证生成的hash值大小一定小于我们计算得到的大小(2^N),正常思维下我们会把hash对数组长度取模运算,但这属实太慢了,远不如位移运算快,改几个电位比取模快多了,于是决定将取模改为位移运算,但问题又来了,采用按位与运算时,为什么一定要2的N次方呢,因为这样可以保证运算出来的值肯定在 2^N -1 内,

例如 当数组长度分别为8 和 7时,hash值为2和3

length - 1


  0000 0011  3                         0000 0010 2
& 0000 0111  7                       & 0000 0111 7
= 0000 0011  3                       = 0000 0010 2
-------------------------------------------------------
  0000 0011  3                         0000 0010 2
& 0000 0110  6                       & 0000 0110 6
= 0000 0010  2                       = 0000 0010 2

会发现当length为 2^N 时,length - 1 进行与运算时hash值总能落在 length 内(得到的值肯定不大于 0111 ),同时对比 length 为 7 时的与运算发现,当 length 为 7 时( 0110 )全部下标为 xxx1 的空间都不可使用,因为对 0110 进行与运算时无法得到 xxx1 的结果。

以上均为个人理解,如有错误请指出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值