1. 程式人生 > 實用技巧 >HashMap 容量為什麼總是為 2 的次冪?

HashMap 容量為什麼總是為 2 的次冪?

作者:Helloworld先生
https;?/blog.csdn.net/u010841296/article/details/82832166

HashMap是根據key的hash值決策key放入到哪個桶(bucket)中,通過 tab=[(n - 1) & hash] 公式計算得出,其中tab是一個雜湊表。

1. 為什麼要保證 capacity 是2的次冪呢?

1)在get方法實現中,實際上是匹配連結串列中的 Node[] tab 中的資料。

(n - 1) & hash實際上是計算出 key 在 tab 中索引位置,當key的hash沒有衝突時,key在HashMap儲存的位置就是匹配的node中的第一個節點。如果hash有衝突,就會在node裡面節點中查詢,直至匹配到相等的key。

2)因為 n 永遠是2的次冪,所以 n-1 通過 二進位制表示,永遠都是尾端以連續1的形式表示(00001111,00000011)

當(n - 1) 和 hash 做與運算時,會保留hash中 後 x 位的 1

例如 00001111 & 10000011 = 00000011

這樣做有2個好處

  • &運算速度快,至少比%取模運算塊

  • 能保證 索引值 肯定在 capacity 中,不會超出陣列長度

  • (n - 1) & hash,當n為2次冪時,會滿足一個公式:(n - 1) & hash = hash % n

2.為什麼要通過 (n - 1) & hash 決定桶的索引呢?

1)key具體應該在哪個桶中,肯定要和key掛鉤的,HashMap顧名思義就是通過hash演算法高效的把儲存的資料查詢出來,所以HashMap的所有get 和 set 的操作都和hash相關。

2)既然是通過hash的方式,那麼不可避免的會出現hash衝突的場景。hash衝突就是指 2個key 通過hash演算法得出的雜湊值是相等的。hash衝突是不可避免的,所以如何儘量避免hash衝突,或者在hash衝突時如何高效定位到資料的真實儲存位置就是HashMap中最核心的部分。

3)首先要提的一點是 HashMap中 capacity 可以在建構函式中指定,如果不指定預設是2 的 (n = 4) 次方,即16。

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

4)HashMap中的hash也做了比較特別的處理,(h = key.hashCode()) ^ (h >>> 16)。

先獲得key的hashCode的值 h,然後 h 和 h右移16位 做異或運算。

實質上是把一個數的低16位與他的高16位做異或運算,因為在前面 (n - 1) & hash 的計算中,hash變數只有末x位會參與到運算。使高16位也參與到hash的運算能減少衝突。

例如1000000的二進位制是

00000000 00001111 01000010 01000000

右移16位:

00000000 00000000 00000000 00001111

異或

00000000 00001111 01000010 01001111

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

3.capacity 永遠都是 2 次冪,那麼如果我們指定 initialCapacity 不為 2次冪時呢,是不是就破壞了這個規則?

答案是:不會的,HashMap 的tableSizeFor方法做了處理,能保證n永遠都是2次冪。

/**
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {
    //cap-1後,n的二進位制最右一位肯定和cap的最右一位不同,即一個為0,一個為1,例如cap=17(00010001),n=cap-1=16(00010000)
    int n = cap - 1;
    //n = (00010000 | 00001000) = 00011000
    n |= n >>> 1;
    //n = (00011000 | 00000110) = 00011110
    n |= n >>> 2;
    //n = (00011110 | 00000001) = 00011111
    n |= n >>> 4;
    //n = (00011111 | 00000000) = 00011111
    n |= n >>> 8;
    //n = (00011111 | 00000000) = 00011111
    n |= n >>> 16;
    //n = 00011111 = 31
    //n = 31 + 1 = 32, 即最終的cap = 32 = 2 的 (n=5)次方
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

關注公眾號Java技術棧回覆"面試"獲取我整理的2020最全面試題及答案。

推薦去我的部落格閱讀更多:

1.Java JVM、集合、多執行緒、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!