【資料結構】29、hashmap=》tableSizeFor 中求大於等於當前數的最小2的冪

cutter_point發表於2019-06-25

 

最近面試被問到hashmap的實現,因為前段時間剛好看過原始碼,顯得有點信心滿滿,但是一頓操作下來的結論是基礎不夠紮實。。。

好吧,因為我開始看hashmap是想了解這到底是一個什麼樣的機制,具體有啥作用,並沒有過於細節去了解,所以問到細節的地方就難免漏洞百出,

回來之後,決定吧容器類的實現原理,去專研一下,目的是為了以後寫程式碼自己可以去優化它

 

好了,不BB了,直接上程式碼,hashmap中有這麼一段程式碼

//容器最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     *
     * @program: y2019.collection.MyHashMap
     * @description: 這個方法用於找到大於等於initialCapacity的最小的2的冪(initialCapacity如果就是2的冪,則返回的還是這個數)。
     * @auther: xiaof
     * 總結:
     * 1.說白了就是為了保證所有的位數(二進位制)都是1,那麼就可以保證這個數就是2的冪
     * 2.不斷做無符號右移,是為了吧高位的資料拉下來做或操作,來保證對應的底位都是1
     * @date: 2019/6/25 10:25
     */
    public static final int tableSizeFor(int cap) {
        //這是為了防止,cap已經是2的冪。如果cap已經是2的冪
        int n = cap - 1;
        //第一次右移,由於n不等於0(如果為0,不管幾次右移都是0,那麼最後有個n+1的操作),則n的二進位制表示中總會有一bit為1
        //這裡無符號右移一位之後做或操作,那麼會導致原來有1的地方緊接著也是1
        //比如00000011xxxxxxxx
        //還有一點無符號右移是為了避免前位補1,導致資料溢位,因為負數是以補碼的形式存在的,那麼就會再高位補1
        n |= n >>> 1;
        //第二次無符號右移,並做或操作
        //00000011xxxxxxxx=>0000001111xxxxxx 這個時候就是4個1
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        //由於int最大也就是2的16次冪,所以到16停止
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

 

我開始的不明白的地方是為什麼要做4次右移???為什麼要做無符號右移???

那麼我手動時間一個low點的版本我們對比一下

public static final int tableSizeFor2(int cap) {
        //這是為了防止,cap已經是2的冪。如果cap已經是2的冪
        int n = cap - 1;
        n |= n & 0xffff;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

原諒我的無知,我的第一反應就是這個,想都沒想為什麼不這樣做。。。

結果發現相差甚遠

 

第三行就是我這第二個方法得到的值,除了吧負數排除之外,沒啥屌用,就是把原來的n去掉符號之後做了一次與運算

 

 這個題的原理是獲取到這個入參的位數,然後獲取2的N次冪

public static final int tableSizeFor3(int cap) {
        //這是為了防止,cap已經是2的冪。如果cap已經是2的冪
        int n = (cap - 1) & 0xffff;
        String hex = Integer.toBinaryString(n);
        return (cap <= 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : (int) Math.pow(2, hex.length());
    }

 

我們再這樣處理

 

我們發現好像很接近了,我們發現n為1的時候,我們得到的長度是2,如果是以大於等於這個數的2的N次冪的話,我覺得我下面這個方法視乎更符合要求

 

 

 接下來我們來試試效能?

 當我們需要計算的數量達到1000000的時候,我們發現,這兩個操作的效能相差有點大。。。

 

 

 

 好吧,結論發現就是,jdk的原始碼不虧是經過千錘百煉的,一些看不懂的操作也許就是故意而為!!!

多關注這些看不懂的操作,學會了你也是大神!!!

 

參考文章:

https://blog.csdn.net/fan2012huan/article/details/51097331

 

相關文章