HashMap 中如何確定元素的位置
衆所周知,在 jdk 1.7 中,HashMap 底層是由數組 + 鏈表的方式實現的,那我們在使用 HashMap 的時候,是如何將我們的 key-value put 到 HashMap 中的呢
HashMap 存放原理
在理解 HashMap 的存放原理前,我們先來回想一下數組,當我們想給數組中的一個元素進行賦值時,我們至少需要知道兩個條件,一是數組的引用名稱,二是想要被賦值的數組元素的索引,即array[i]
中的array
和i
我們再來看看,在 jdk 1.7 中 HashMap 的結構:
從上圖我們可以看到,HashMap 的主體是一個數組,這個數組中存儲的元素是一個Entry<k, v>
類型的變量,這個變量有四個屬性:Object key
、Object value
、int hash
、Entry next
,當兩個元素經過計算後的數組下標相同時,就是所謂的發生了 Hash 碰撞,這時,就需要在數組發生 Hash 碰撞的位置構造一個鏈表,將發生碰撞的元素以鏈表的形式,存放在數組中
瞭解了 HashMap 的 構成,我們知道 HashMap 的本質也是數組,既然我們想向數組裏 put 元素,那我們必然需要知道數組的引用名稱和要被 put 的位置的下標,可是 HashMap 的 put 方法只有 key 和 value 兩個參數,沒有 int 類型的 index,那 HashMap 是如何確定每個元素會被存放到數組的哪個位置呢?
這裏我們看一下 HashMap 源碼中的 put 部分:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
addEntry(hash, key, value, i);
return null;
}
這裏我們暫且不討論其他部分,直接看第 8 行代碼,結合下面的table[i]
等變量可以看到,這行代碼通過indexFor
函數返回的值,正是經過計算的數組下標,也就是說,HashMap 會在 put 元素時,通過元素的 hash 值以及當前數組的長度,來確定一個下標來存放元素
這時我們不妨想一想,hash 值一般都是一個十分巨大的整數,例如12345643
、327864322
等等(都是我瞎打的),而數組的長度一定是一個十分有限的數,假設是8
,我們正常想通過這兩個數來獲取一個0~7
的正數下標要怎麼做?毫無以爲,用hash % table.length
,這樣不管 hash 是一個多大的數,我們都可以得到一個在數組索引範圍內的整數,那 HashMap 的 indexFor 函數中是如何做的呢?
HashMap 的 indexFor 函數
這裏我們直接看 indexFor 函數的代碼:
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
代碼很短,只有一行,我們可以看到,HashMap 中是通過 hash 和數組長度減一得到的結果進行一次&
運算,這裏我們要先清楚&
運算的概念:將兩個二進制數進行按位&
操作時,只有兩個數對應的位上都爲 1,結果爲 1,否則都爲 0
我們不妨帶進來一個數算一遍,這樣結果比較直接,假設我們的 h 的二進制表示是1101 0110
(我瞎編的),數組的長度是8
,二進制就是0000 1000
,這時我們先進行length - 1
的操作,得到0000 0111
,這時再與 hash 進行&
操作時,可以得到0000 0110
,即十進制的6
,而 HashMap 的容量,即數組的長度永遠都是2
的次方,也就是說,table.length
的二進制表示永遠都是一個1
,其餘都是0
的狀態,例如2
的4
次方16
是0001 0000
,5
次方32
是0010 0000
,那也就是說明,table.length - 1
得到的值永遠都是前一半都是0
,後一半都是1
,這種結構再與 hash 進行&
操作時,得到的結果就和hash % table.length
一樣了!