爲何 HashMap 的容量大小要設置爲2的N次方?

原文連接:https://www.changxuan.top/?p=1208web


前兩天,我在一位同窗提交中看到了下面這樣的一行代碼,讓我非常驚訝。數組

Map<String, String> temp = new HashMap<>(6);

我給他說,你這樣實例化 Map 對象很差用,他不服氣。我說小朋友:若是想指定 HashMap 對象的容量得用2的N次方。他說你這也沒用。我說,我這個有用,這樣才能充分利用分配的內存空間。他非和我試試,我說能夠,不過得先一塊兒看看源碼。安全

什麼是HashMap?

在弄懂標題的問題以前,首先須要清楚 HashMap 的概念。HashMap 是基於哈希表的 Map 接口的實現,線程不安全,且不保證映射順序。編輯器

HashMap 存儲數據依賴的是數組和[鏈表|紅黑樹],具體鏈表和紅黑樹之間如何轉換的細節此文不作詳細介紹。而本文開頭提到的實例化容量大小指的則是數組的大小。url

如何計算元素在數組中所對應的下標?

首先計算元素的哈希值,方法以下:spa

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

爲何不直接使用 key.hashCode()的值,咱們後面會提到。線程

計算出來哈希值後,因爲數組容量相對來講較小確定不能直接使用哈希值看成索引值。因此須要使用哈希值對數組長度減一後的值取模。不過在在 HashMap 中可不是直接使用 % 運算符來操做的。爲了提升效率,採用的是與運算的方式,代碼以下:code

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict)
 
{
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
      // n 爲數組容量, (n-1) & hash 則是計算索引值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
          ... ...
        }
}

既然清楚了計算元算在數組中所對應下標的方法,那麼證實爲何實例化 HashMap 對象的容量要使用2的N次方就簡單多了。對象

假如初始容量爲2的3次方數字8,當哈希值與容量大小減一的值進行與運算時能夠保證結果比較均勻的分佈在數組上。索引

  10100101 11000100 00100101
& 00000000 00000000 00000111 // 7
----------------------------------
  00000000 00000000 00000101 // 結果能夠是[0,7]中的任一數字

若是初始容量爲6,那麼出現哈希衝突的概率就會增長了。

  10100101 11000100 00100101
& 00000000 00000000 00000101 // 5 
----------------------------------
  00000000 00000000 00000101 // 5
  
  10100101 11000100 00100111
& 00000000 00000000 00000101 // 5 
----------------------------------
  00000000 00000000 00000101 // 5

若是下面的值低位全是1,那麼上面的此次哈希衝突則能夠避免。那麼你想一想,假如指定的容量大小爲5又會怎麼樣呢?其實2的N次方數字-1的二進制形式這個特性在好多地方會很好用,能夠在小本本記上。

哦,前面說爲何計算出來的散列值須要再讓高16位和低十六位作異或運算,主要是讓參與與運算的位同時具備高位和低位的特徵,來減小哈希碰撞次數。

小朋友,還試不試啦!

相關文章
相關標籤/搜索