JDK8下HashMap的tableSizeFor方法

版权声明:转载请注明原址,有错误欢迎指出 https://blog.csdn.net/iwts_24/article/details/87360318

        感觉HashMap跟其他的容器风格都不太一样,后来看网上是说JDK8相比以前对HashMap的修改非常大。在看构造方法的时候就卡在了tableSizeFor方法。先看一下构造方法:

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

loadFactor是加载因子,而initialCapacity首先想到的就是给散列表一个初始容量——实际上在jdk7之前的版本,这个构造方法最后就是:

table = new Entry[capacity];

但是jdk8却调用了tableSizeFor方法并赋值给了threshold变量。这里应该看一下HashMap中对于散列表table的注释:

The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)

谷歌尬翻一下,大概意思是,初始化的时候使用threshold给赋一个初始值,并且要求值是2的幂。这个就是核心了:如何保证该值一定是2的幂,因为传入的值可不一定是2的幂。

        tableSizeFor方法,就是将该值进行扩大:保证能够一定是2的幂,从而提供了算法。2的幂在HashMap中很重要,连最大值都是2^30,而不是ArrayList的2^31-10。先看源码:

    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的最小的2的幂。常规算法累加后判定,巨慢。而考虑二进制的话:2的幂一定是一个1后面跟一堆0的情况。那么问题就化简了,例如给出一个10,其二进制为1010,在限定位的时候,2的幂之间是一个范围。例如16也就是2^4,说明在5位的时候,10000,而8是1000,之间的所有数都介于1000和10000之间。

        那么我们可以按位或运算,保证将所有的0置1。但是此时又出现了问题:虽然我们看起来10的二进制是1010,但在计算机中实际上是0000 0000 0000 1010。如果直接按位或,会将高位也置1,或者说,我们应该跟谁或?而这个算法就解决了这个问题。连续5次,先移位,再按位或,以10为例子:

n |= n >>> 1    // 1010 |= 0101
                // n = 1111
n |= n >>> 2    // 1111 |= 0011
                // n = 1111
...

实际上,第一次操作就将所有位置1了,而根据或运算的特性,后面的5次操作对结果没有任何影响。每次移位,就将1忘后面移,这样就能保证每一位都能变成1,并且是在n的基础上移动,保证了最后高位是不变的。我们认为只要最后得到全1的情况就行了因为最后:

return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

我们让n+1了,而全1的情况+1一定变成1后面跟一堆0的情况,这样就确定了最终的值。

猜你喜欢

转载自blog.csdn.net/iwts_24/article/details/87360318