最近面試被問到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