本文主要有關(guān)HashMap1.8源碼分析
原理和過程
1:HashMap的原理厘肮,內(nèi)部數(shù)據(jù)結(jié)構(gòu)如何吉懊?
底層使用哈希表(數(shù)組+鏈表)褐澎,當(dāng)鏈表過長(其實(shí)是大于8)的時(shí)候會(huì)將鏈表轉(zhuǎn)換成紅黑樹疮胖,以實(shí)現(xiàn)n log(n) 的查找俯萎,每一個(gè)節(jié)點(diǎn)Node包括key凉袱,value芥吟,Node,Next指針。
[圖片上傳失敗...(image-eeb83d-1551951086064)]
2:具體過程
- 1:對 Key 求 Hash 值绑蔫,然后再計(jì)算 下標(biāo)运沦。
- 2:如果沒有碰撞,直接放入桶中配深,
- 3:如果碰撞了携添,以鏈表的方式鏈接到后面,
- 4:如果鏈表長度超過閥值(TREEIFY_THRESHOLD == 8)篓叶,就把鏈表轉(zhuǎn)成紅黑樹烈掠。
- 5:如果節(jié)點(diǎn)已經(jīng)存在就替換舊值
- 6:如果桶滿了(容量 * 加載因子)羞秤,就需要 resize。
- i:在調(diào)用put方法的時(shí)候左敌,底層直接調(diào)用的是putVal方法
源碼分析
putAll()方法
putVal(hash(key), key, value, false, true);
hash(key) 方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
為什么不上直接返回key.hashCode()---這是由jvm決定的瘾蛋,就是key的地址;而是返回key的hashCode 該值的高16位與其低16位異或后的值呢
首先矫限,h是32位的哺哼,而采用以后運(yùn)算得到的0和1是均勻的,因?yàn)槲覀儜?yīng)該盡量使這個(gè)值不同叼风,& | 運(yùn)算都會(huì)偏向0或1取董,如下圖所示
[圖片上傳失敗...(image-c13d0d-1551951086064)]
putVal(.......)方法源碼分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 其中Node就是K和V的一個(gè)節(jié)點(diǎn),一個(gè)內(nèi)部類
Node<K,V>[] tab;
//定義一個(gè)p節(jié)點(diǎn)
Node<K,V> p; int n, i;
//table是Node數(shù)組无宿,其默認(rèn)length為16 (2的n次冪茵汰,為什么要這樣呢 有一定道理的,后面解釋)
if ((tab = table) == null || (n = tab.length) == 0)
//如果tab為空孽鸡,或者長度為0蹂午,說明map中還沒有存放value
n = (tab = resize()).length; //此時(shí)需要調(diào)用resize()方法擴(kuò)容,這里是初始化彬碱,這個(gè)方法后面分析
if ((p = tab[i = (n - 1) & hash]) == null)
//說明這個(gè)位置為空豆胸,直接生成一個(gè)K和V的Node節(jié)點(diǎn),插入即可
tab[i] = newNode(hash, key, value, null);
//為什么 i = (n - 1) & hash呢 而不是直接 hash % n 呢
//假設(shè)n的值為16巷疼,-1的話為15 : 000....001111
// hash : 1010....11010 //所以明顯得到的值的范圍為(0-15)配乱,和%運(yùn)算一樣,
//可效率明顯&高哦皮迟,直接轉(zhuǎn)為2進(jìn)制了
//該位置有Node了,hash碰撞了桑寨,這時(shí)候有三種處理情況
//(1)key相同 伏尼,直接替換為新的節(jié)點(diǎn)
//(2)key不同,用鏈表存儲
//(3) key不同尉尾,用紅黑樹存儲
else {
Node<K,V> e; K k;
//key相同爆阶,直接替換
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//紅黑數(shù)方式存儲(需要判斷該節(jié)點(diǎn)是在左邊還是右邊,還要左旋和右旋進(jìn)行調(diào)整)
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//鏈表沙咏,尾插法存儲
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //判斷p節(jié)點(diǎn)下一節(jié)點(diǎn)是否為空
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//鏈表轉(zhuǎn)紅黑樹
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //這種情況會(huì)執(zhí)行下面的if(e != null)
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) //這個(gè)threshold就是用下判斷是否需要擴(kuò)容的
resize();
afterNodeInsertion(evict);
return null;
}
問題
-
1:HashMap 中 hash 函數(shù)怎么是是實(shí)現(xiàn)的辨图?
- 1:高 16bit 不變,低 16bit 和高 16bit 做了一個(gè)異或
- 2:(n - 1) & hash --> 得到下標(biāo)
2:數(shù)組每個(gè)位置上的鏈表長度為什么要限制長度呢肢藐?
如果在某個(gè)位置上發(fā)生過多hash碰撞故河,在put的時(shí)候就會(huì)發(fā)生順延,要從頭到尾比遍歷這個(gè)鏈表吆豹,
效率低 (put的時(shí)候是尾插法)鱼的,所以在jdk1.8的時(shí)候長度超過8的時(shí)候就轉(zhuǎn)為紅黑樹理盆。
[圖片上傳失敗...(image-dbe133-1551951086064)]3:怎么減少hash碰撞,盡可能的使數(shù)組位置都用到凑阶?
i = (n-1) & hash,所以是由n 和 hash控制的猿规,顯然我們能夠操作的是hash,就是通過key的hashCode的高16位和低16異或宙橱。4:為什么n一定要是n的2次冪呢姨俩?
其實(shí)就是為了保證在計(jì)算i的時(shí)候,在&操作的時(shí)候师郑,(n -1)獲的每一位都為1环葵,此時(shí)i的值才取決有hash。-
5:HashMap 怎樣解決沖突呕乎,講一下擴(kuò)容過程积担,假如一個(gè)值在原數(shù)組中,現(xiàn)在移動(dòng)了新數(shù)組猬仁,位置肯定改變了帝璧,那是什么定位到在這個(gè)值新數(shù)組中的位置(可看下面擴(kuò)容源碼分析解釋)
- a:將新節(jié)點(diǎn)加到鏈表后,
- b:容量擴(kuò)充為原來的兩倍湿刽,然后對每個(gè)節(jié)點(diǎn)重新計(jì)算哈希值的烁。
- c:這個(gè)值只可能在兩個(gè)地方,一個(gè)是原下標(biāo)的位置诈闺,另一種是在下標(biāo)為 <原下標(biāo)+原容量> 的位置渴庆。
6:幾個(gè)參數(shù)意思
//數(shù)組默認(rèn)的容量:1左移4位 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量 2的30次冪
static final int MAXIMUM_CAPACITY = 1 << 30;
//加載因子,用來乘以容量雅镊,算出一個(gè)判斷擴(kuò)容的數(shù)值
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//鏈表長度超過這個(gè)長度后轉(zhuǎn)為紅黑樹
static final int TREEIFY_THRESHOLD = 8;
擴(kuò)容
擴(kuò)容源碼分析:雙倍擴(kuò)容(保證容量為2的n次冪)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { //不能再擴(kuò)容了
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 擴(kuò)容后襟雷,這個(gè)閾值也要翻倍
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//oldCap = 0 ,這里就是初始化
newCap = DEFAULT_INITIAL_CAPACITY;
//這個(gè)數(shù)值是干嘛的呢,就是用來判斷什么時(shí)候開始對這個(gè)數(shù)組進(jìn)行擴(kuò)容仁烹,
// 容量*加載因子 < 容量
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //擴(kuò)容數(shù)組
table = newTab;
//擴(kuò)容之后耸弄,之前有的元素位置必須要調(diào)整,進(jìn)行遷移
if (oldTab != null) {
//數(shù)組對應(yīng)位置下面沒元素 是鏈表 是紅黑樹
//開始循環(huán)遍歷數(shù)組
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e; //得到新的位置
else if (e instanceof TreeNode) //紅黑樹
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
//e.hash & oldCap 對這個(gè)進(jìn)行分析 加入oldCap = 16
//我們之前計(jì)算i的時(shí)候是通過 hash & oldCap -1 (15:01111);
//而在擴(kuò)容的時(shí)候 用 hash & oldCap (16:10000)這個(gè)值與0 比較
//如果等于0 能說明什么呢 即hash的二級制的倒數(shù)第5位為0
//newCap = 32 新的 i = hash & 31 (11111) 會(huì)等于 hash & 15(01111)
//即在數(shù)組中的位置沒變
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else { //不是為0 i的值會(huì)等于 之前的位置 + oldCap
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead; //巧妙把卓缰,不用重新算了
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead; //巧妙把计呈,看while中的循環(huán)解釋
}
}
}
}
}
return newTab;
}