- https://blog.csdn.net/v123411739/article/details/78996181/
- 分析put流程
HashMap<key,value>
裝箱
key->hashCode:int->return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16
1.執(zhí)行hashCode位運(yùn)算,計(jì)算出具體的數(shù)組index下標(biāo)
public V put(K key, V value) {
return this.putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}
2.執(zhí)行putValu進(jìn)行值計(jì)算
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
1.校驗(yàn)table是否為空或者length等于0,如果是則調(diào)用resize方法進(jìn)行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
2.通過hash值計(jì)算索引位置,將該索引位置的頭節(jié)點(diǎn)賦值給p,如果p為空則直接在該索引位置新增一個(gè)節(jié)點(diǎn)即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
3.判斷p節(jié)點(diǎn)的key和hash值是否跟傳入的相等,如果相等, 則p節(jié)點(diǎn)即為要查找的目標(biāo)節(jié)點(diǎn),將p節(jié)點(diǎn)賦值給e節(jié)點(diǎn)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
4.判斷p節(jié)點(diǎn)是否為TreeNode, 如果是則調(diào)用紅黑樹的putTreeVal方法查找目標(biāo)節(jié)點(diǎn)
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
5.走到這代表p節(jié)點(diǎn)為普通鏈表節(jié)點(diǎn),則調(diào)用普通的鏈表方法進(jìn)行查找,使用binCount統(tǒng)計(jì)鏈表的節(jié)點(diǎn)數(shù)
for (int binCount = 0; ; ++binCount){}
else {
走到這代表p節(jié)點(diǎn)為普通鏈表節(jié)點(diǎn),則調(diào)用普通的鏈表方法進(jìn)行查找,使用binCount統(tǒng)計(jì)鏈表的節(jié)點(diǎn)數(shù)
for (int binCount = 0; ; ++binCount) {
// 6.如果p的next節(jié)點(diǎn)為空時(shí),則代表找不到目標(biāo)節(jié)點(diǎn),則新增一個(gè)節(jié)點(diǎn)并插入鏈表尾部
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 7.校驗(yàn)節(jié)點(diǎn)數(shù)是否超過8個(gè),如果超過則調(diào)用treeifyBin方法將鏈表節(jié)點(diǎn)轉(zhuǎn)為紅黑樹節(jié)點(diǎn),
// 減一是因?yàn)檠h(huán)是從p節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)開始的
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 8.如果e節(jié)點(diǎn)存在hash值和key值都與傳入的相同,則e節(jié)點(diǎn)即為目標(biāo)節(jié)點(diǎn),跳出循環(huán)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; // 將p指向下一個(gè)節(jié)點(diǎn)
}
}
// 9.如果e節(jié)點(diǎn)不為空,則代表目標(biāo)節(jié)點(diǎn)存在,使用傳入的value覆蓋該節(jié)點(diǎn)的value,并返回oldValue
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); // 用于LinkedHashMap
return oldValue;
}
}
++modCount;
// 10.如果插入節(jié)點(diǎn)后節(jié)點(diǎn)數(shù)超過閾值,則調(diào)用resize方法進(jìn)行擴(kuò)容
if (++size > threshold)
resize();
執(zhí)行g(shù)et方法,如果是紅黑樹,則調(diào)用紅黑樹的查找目標(biāo)節(jié)點(diǎn)方法getTreeNode
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 1.對(duì)table進(jìn)行校驗(yàn):table不為空 && table長度大于0 &&
// table索引位置(使用table.length - 1和hash值進(jìn)行位與運(yùn)算)的節(jié)點(diǎn)不為空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 2.檢查first節(jié)點(diǎn)的hash值和key是否和入?yún)⒌囊粯?,如果一樣則first即為目標(biāo)節(jié)點(diǎn),直接返回first節(jié)點(diǎn)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 3.如果first不是目標(biāo)節(jié)點(diǎn),并且first的next節(jié)點(diǎn)不為空則繼續(xù)遍歷
if ((e = first.next) != null) {
if (first instanceof TreeNode)
// 4.如果是紅黑樹節(jié)點(diǎn),則調(diào)用紅黑樹的查找目標(biāo)節(jié)點(diǎn)方法getTreeNode
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
// 5.執(zhí)行鏈表節(jié)點(diǎn)的查找,向下遍歷鏈表, 直至找到節(jié)點(diǎn)的key和入?yún)⒌膋ey相等時(shí),返回該節(jié)點(diǎn)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
// 6.找不到符合的返回空
return null;
}
3.DEFAULT_INITIAL_CAPACITY=16?
在線位運(yùn)算
在線二進(jìn)制轉(zhuǎn)換
以下僅演示低4位的位運(yùn)算,其實(shí)在jdk1.8的時(shí)候位把高28位的運(yùn)算都加入,以此來降低hash沖突的概率
如果length默認(rèn)是16,那么length-1=15,執(zhí)行下面的與運(yùn)算
h&length-1
15 二進(jìn)制: 1111
& 0 二進(jìn)制: 0000
----------------------------
1111
====================
15 二進(jìn)制: 1111
& 1 二進(jìn)制: 0001
----------------------------
1110
如果length默認(rèn)是15,那么length-1=14,執(zhí)行下面的與運(yùn)算
h&length-1
14 二進(jìn)制: 1110
& 0 二進(jìn)制: 0000
----------------------------
1110--------------------------->1110跟下面這個(gè)值一樣
====================
14 二進(jìn)制: 1110
& 1 二進(jìn)制: 0001
----------------------------
1110--------------------------->1110跟上面這個(gè)值一樣
綜上分析,如果默認(rèn)值不是16,或者說不是2的冪指數(shù),那么執(zhí)行位運(yùn)算之后,得出的結(jié)果可能是相同的,也就是哈希碰撞的概率變高
3.TreeNode 紅黑樹(高性能的平衡樹)來保存沖突節(jié)點(diǎn)O(n)->提高到O(logn)
可以考慮下時(shí)間復(fù)雜度是怎么計(jì)算的,這里備注下
4.是否每一個(gè)節(jié)點(diǎn)都是紅黑樹?
不是,為了效率更高,并不會(huì)直接每個(gè)節(jié)點(diǎn)都使用紅黑樹,因?yàn)榧t黑樹的初始化復(fù)雜,而且有很多的左旋右旋,本身來說,構(gòu)建復(fù)雜耗時(shí)。
當(dāng)節(jié)點(diǎn)超過8的時(shí)候,才會(huì)轉(zhuǎn)成紅黑樹
目的就是為了讓查找效率跟高效
5.HashMap線程安全問題
多線程插入、刪除、擴(kuò)充操作數(shù)據(jù)的時(shí)候,會(huì)有多線程安全問題,
可以參考HashTable實(shí)現(xiàn)策略
或者SparseArray
6.HashTable 源碼分析
基本來說就是把整個(gè)HashMap加了鎖,其他跟HashMap基本一致
7.ConcurrentHashMap跟HashTable、HashMap區(qū)別
鎖的范圍精確到對(duì)應(yīng)鏈表,而不是整個(gè)HashMap
7.SparseArray 源碼分析 跟HashMap區(qū)別
8.HashMap為什么會(huì)發(fā)生死循環(huán)
https://baijiahao.baidu.com/s?id=1675991555833901875&wfr=spider&for=pc
待總結(jié)