1:hashmap的原理,內部資料結構如何?
底層使用雜湊表(陣列+鍊錶),當鍊表過長(其實是大於8)的時候會將鍊錶轉換成紅黑樹,以實現n log(n) 的查詢。
2:具體過程
對 key 求 hash 值,然後再計算 下標。
如果沒有碰撞,直接放入桶中,
如果碰撞了,以鍊錶的方式鏈結到後面,
如果鍊錶長度超過閥值(treeify_threshold == 8),就把鍊錶轉成紅黑樹。
如果節點已經存在就替換舊值
如果桶滿了(容量 * 載入因子),就需要 resize。
3 : 原始碼分析
putval(hash(key), key, value, false, true);
//hash()方法
static final int hash(object key)
為什麼不直接返回key.hashcode()---這是由jvm決定的,就是key的位址;而是返回key的hashcode 該值的高16位與其低16位異或後的值呢
首先,h是32位的,而採用以後運算得到的0和1是均勻的,因為我們應該盡量使這個值不同,& | 運算都會偏向0或1,如下圖所示
putval(.......)方法原始碼分析
final v putval(int hash, k key, v value, boolean onlyifabsent,
boolean evict)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //這種情況會執行下面的if(e != null)
p = e;}}
v oldvalue = e.value;
if (!onlyifabsent || oldvalue == null)
e.value = value;
afternodeaccess(e);
return oldvalue;}}
++modcount;
if (++size > threshold) //這個threshold就是用下判斷是否需要擴容的
resize();
afternodeinsertion(evict);
return null;
}
4:問題hashmap 中 hash 函式怎麼是是實現的?
高 16bit 不變,低 16bit 和高 16bit 做了乙個異或
(n - 1) & hash --> 得到下標
陣列每個位置上的鍊錶長度為什麼要限制長度呢?
如果在某個位置上發生過多hash碰撞,在put的時候就會發生順延,要從頭到尾比遍歷這個鍊錶,效率低(put的時候是尾插法),所以在jdk1.8的時候長度超過8的時候就轉為紅黑樹。
怎麼減少hash碰撞,盡可能的使陣列位置都用到?
i = (n-1) & hash,所以是由n 和 hash控制的,顯然我們能夠操作的是hash,就是通過key的hashcode的高16位和低16異或。
為什麼n一定要是n的2次冪呢?
其實就是為了保證在計算i的時候,在&操作的時候,(n -1)獲的每一位都為1,此時i的值才取決有hash。
hashmap 怎樣解決衝突,講一下擴容過程,假如乙個值在原陣列中,現在移動了新陣列,位置肯定改變了,那是什麼定位到在這個值新陣列中的位置(可看下面擴容原始碼分析解釋)
將新節點加到鍊錶後,
容量擴充為原來的兩倍,然後對每個節點重新計算雜湊值。
這個值只可能在兩個地方,乙個是原下標的位置,另一種是在下標為 《原下標+原容量》 的位置。
幾個引數意思
//陣列預設的容量:1左移4位 16
static final int default_initial_capacity = 1 << 4; // aka 16
//最大容量 2的30次冪
static final int maximum_capacity = 1 << 30;
//載入因子,用來乘以容量,算出乙個判斷擴容的數值
static final float default_load_factor = 0.75f;
//鍊錶長度超過這個長度後轉為紅黑樹
static final int treeify_threshold = 8;
5:擴容原始碼分析:雙倍擴容(保證容量為2的n次冪)
final node resize()
else if ((newcap = oldcap << 1) < maximum_capacity &&
oldcap >= default_initial_capacity)
newthr = oldthr << 1; // 擴容後,這個閾值也要翻倍
}else if (oldthr > 0) // initial capacity was placed in threshold
newcap = oldthr;
else
if (newthr == 0)
threshold = newthr;
@suppresswarnings()
node newtab = (node)new node[newcap]; //擴容陣列
table = newtab;
//擴容之後,之前有的元素位置必須要調整,進行遷移
if (oldtab != null)
else
} while ((e = next) != null);
if (lotail != null)
if (hitail != null) }}
}}return newtab;
}
原始碼分析 HashMap 1 8
1.0 資料結構 2.0 儲存流程 3.0 陣列元素 鍊錶節點的實現類 hashmap中的陣列元素 鍊錶節點 採用node類 實現,與jdk1.7相比只是把entry換了個名字 hashmap中的紅黑樹節點 採用treenode類 實現 紅黑樹節點 實現類 繼承自linkedhashmap.entr...
HashMap 1 8 原始碼閱讀
一 初始化 1.無參建構函式 負載因子預設值 static final float default load factor 0.75f 指定loadfactor負載因子的值是0.75f public hashmap 2.指定初始化大小和負載因子 hashmap的最大容量 static final i...
JDK原始碼分析系列 HashMap 1 8
預設的初始化容量,必須是2的n次冪 static final int default initial capacity 1 4 aka 16 最大的容量是2的30次冪 static final int maximum capacity 1 30 預設的負載因子 static final float ...