HashMap中哈希表的長度為什么需要是2的冪次方以及怎么實現


看過HashMap源碼的人可能都用印象,就是hashMap的哈希表長度可以由自己指定也可以不指定使用默認長度,但是如果在了解或者發現tableSizeFor方法的話,你就會知道此方法會改變我們的輸入長度 (如果我們輸入15,他會改為16),那么他為什么要修改我們設置的長度,以及修改后有什么作用?帶着這個疑問我們往下看;

1. HashMap 的長度為什么需要是2的冪次方

為了能讓hashMap存取高效,盡量減少碰撞,也就是要盡量把數據分配均勻。
Hash值的取值范圍-2147483648到2147483647,總共有40+億個映射空間,只要哈希函數映射的比較均勻,一般應用很難出現碰撞,但是內存肯定不能一次加載這么長的數組,所以這個散列值是不能拿來直接用的,我們只能創建合理長度的數組作為哈希表,在插入數據之前做取模運算,得到的余數就是將要存放的數據在哈希表中對應的下標。在HashMap中這個下標的取值算法是:(n - 1) & hash n是哈希表的長度。

取模運算中如果除數是2的冪次方則等價於 其與除數減一的&操作,就是:hash % length == hash & (length - 1)
采用二進制位操作 & 相對於 % 能夠提高運算效率,這也就解釋了為啥HashMap的長度需要為2的冪次方

2. HashMap怎么實現的

先看下JDK8的源碼:
/**
 * 方法保證了HashMap的哈希表長度總位2的冪次方
 * 返回大於輸入參數且最近的2的整數次冪的數
 */
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;
}

我也是看到這個方法后才決定寫這篇博文來記錄一下我的感想,我已經不知道多少次被源碼作者所折服,我甚至覺得這才是代碼應該的樣子;

結果分析
  n第一次右移一位時,相當於將最高位的1右移一位,再和原來的n取或,就將最高位和次高位都變成1,也就是兩個1;

  第二次右移兩位時,將最高的兩個1向右移了兩位,取或后得到四個1;

  依次類推,右移16位再取或就能得到32個1;

你是不是不知道要32個1干嘛?自己體會如下:

2的冪次方 二進制表示 十進制標識
2^0 1 (1-1)+1
2^1 10 (2-1)+1
2^2 100 (4-1)+1
2^3 1000 (8-1)+1
2^4 10000 (16-1)+1
2^5 100000 (32-1)+1

二進制數字如果都是1的話,那么他加一后就是首位為1其他位都是0,這個數字肯定是2的冪次方, 2^n == 1<<n

舉例說明:
  10的二進制是1010,減1就是1001

  第一次右移取或: 1001 | 0100 = 1101 ;

  第二次右移取或: 1101 | 0011 = 1111 ;

  第三次右移取或: 1111 | 0000 = 1111 ;

  第四次第五次同理

  最后得到 n = 1111  ,返回值是 n+1 = 2 ^ 4 = 16 ;  
自己實驗一把
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    System.out.println(Integer.toBinaryString(n));
    n |= n >>> 2;
    System.out.println(Integer.toBinaryString(n));
    n |= n >>> 4;
    System.out.println(Integer.toBinaryString(n));
    n |= n >>> 8;
    System.out.println(Integer.toBinaryString(n));
    n |= n >>> 16;
    System.out.println(Integer.toBinaryString(n));
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

public static void main(String[] args) {
        int a =  65538;
        System.out.println(Integer.toBinaryString(a));
        int i = tableSizeFor(a);
        System.out.println(i);
}
執行結果:
  10000000000000010
  11000000000000001
  11110000000000001
  11111111000000001
  11111111111111111
  11111111111111111
  131072

看到這里是不是也有中被折服的感覺?那就對了,路還長,編碼還要繼續,且看且學習。。。 _


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM