初印象:初識HashMap時,知道HashMap是用來存放Key-Value這樣的鍵值對的,也知道HashMap的底層數據結構是:數組+鏈表+紅黑樹,且數組長度爲2的x次冪。
疑問:那麼往HashMap中添加鍵值對時,是什麼決定了鍵值對的存放位置呢?即存放位置是如何計算出來的呢?相同的疑問可能還會以下面的問題描述方式提出來:
其他描述方式:
1.向HashMap中put數據時,數據是如何找到HashMap中Node<K,V>[] table數組的對應索引下標位置的?
2.HashMap在put<K,V>時是如何找到要存放的數組索引下標的位置的?鍵值對是如何與數組索引下標產生映射關聯關係的?
比如有個HashMap的數組中有16個索引下標位置,一個數據過來要put到HashMap中,是誰來決定該元素會被分配到具體哪個索引下標位置的?如何保證哪塊代碼處理的?
分析:既然是由put方法引出的問題,自然要到put的源代碼裏找尋線索。當我們認真研讀put的源碼時會發現下面這三段代碼,我們分別解讀一下:
- put調用putVal方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} - 根據Key-鍵值計算出新的哈希碼值:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
這段代碼裏首先調用Object的hashCode()方法,計算出key的原始哈希碼;
然後,將原始哈希碼無符號右移了16位,即高16位被移到了低16位(
問1:爲啥做無符號右移操作,即爲啥要從高位往低位移動呢?移動後又爲何要做異或操作?
答1:低位不確保有沒有1,但高位肯定有1,拿無符號右移後的值與原值做異或操作,可以得到一個1的分佈在高低位相對更加均勻的結果。(爲啥要得到這樣的結果呢?是爲了在跟數組長度一起做&與運算計算索引下標時,得到相對更加均勻分撒的結果,這樣根據不同key得出的數組索引下標儘可能分撒的話,就不容易發生哈希碰撞,也就降低了一開始往HashMap中添加數據時鏈表的產生機率)
問2:爲啥又非得是16位呢?
答2:因爲hashCode是一個int整形32位,高16位無符號右移16位,可以保證高位與低位能充分混合,這樣的話,再拿無符號右移後的值與原值做異或操作,可以使得1在高低位的分佈更加均勻。
)
然後無符號右移後的值與原哈希碼值做異或操作,得到一個1的分佈在高低位相對更加均勻的新哈希碼值。 - 根據數組長度及新哈希碼計算索引位置:
在put調用的putVal方法的裏,有這麼一段代碼:
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;
if ((p = tab[i = (n - 1) & hash]) == null)
………………………………後續代碼省略…………………………………………
注意第2個if語句裏隱藏了這麼一句:p = tab[i = (n - 1) & hash]
這裏就是根據數組長度和新哈希碼值計算數組下標的地方,其中:
tab從第三段代碼可以看到是從table得來的,而table就是HashMap中的存放鏈表頭節點的數組:Node<K,V> table;
n是數組的長度,爲2的x次冪的數,故(n-1)代表的二進制位全是1;
hash是從第二段代碼中得到的新哈希值
由上可以進而推導出,在公式(n-1)&hash中:
當hash全爲0時,得到最小值爲0;
當hash全爲1時,得到最大值爲(n-1);
當hash部分爲0部分爲1時,得到介於最大值和最小值之間的整數;
這樣就確保了,根據就根據key得到了要存放的數組索引下標,而且該下標肯定會落在數組長度對應的索引範圍內。