Java的HashMap源碼解析
前言
以jdk1.8為例,HashMap是一個用于存儲Key-Value鍵值對的集合,每一個鍵值對是一個Node(jdk1.7叫做Entry)。后臺是用一個Node數(shù)組來存放數(shù)據(jù),這個Node數(shù)組就是HashMap的主干。
這里我們主要來分析HashMap的get和put方法。
put
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //如果是第一次put,就進(jìn)行數(shù)組的大小初始化,默認(rèn)是16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //根據(jù)hash值,找到在數(shù)組中的位置,如果此位置沒有值,就new一個新的node插入 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //如果數(shù)組該位置有值 else { Node<K,V> e; K k; //判斷該位置節(jié)點(diǎn)的key是否和即將插入的key相等,相等就取出來等待覆蓋 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //若果該節(jié)點(diǎn)是紅黑樹,則調(diào)用紅黑樹相關(guān)方法 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //到了這里,說明該節(jié)點(diǎn)是鏈表,調(diào)用鏈表相關(guān)方法 else { for (int binCount = 0; ; ++binCount) { //循環(huán)到最后一個節(jié)點(diǎn),然后插入新節(jié)點(diǎn)(1.7是往頭結(jié)點(diǎn)插入,1.8是往尾部插入) if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //判斷插入后的該鏈表的長度,如果大于8,就轉(zhuǎn)成紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //這里表示鏈表中某個節(jié)點(diǎn)的key與即將插入的key相等,就跳出循環(huán)等待覆蓋 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //這里表示有節(jié)點(diǎn)的key與新的key相等,那么就覆蓋 if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //插入完之后,如果導(dǎo)致size超過了預(yù)設(shè)的閾值,就進(jìn)行擴(kuò)容(1.7是插入前判斷,1.8是插入后判斷) if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
擴(kuò)容步驟:
1、創(chuàng)建一個原數(shù)組兩倍大小的新數(shù)組,并且把閾值擴(kuò)大一倍。
2、遍歷原數(shù)組,進(jìn)行數(shù)據(jù)遷移。分為紅黑樹和鏈表兩種情況。
好了,擴(kuò)容部分就不展開代碼詳細(xì)說明,接下來進(jìn)入get方法,相較于put方法就沒那么復(fù)雜了,且代碼量也比較少
get
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; //判斷底層node數(shù)組是否為空及該hash值對應(yīng)的數(shù)組位置是否有值 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //判斷該數(shù)組的節(jié)點(diǎn)是不是我們需要的值,是就直接返回 if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { //該節(jié)點(diǎn)是紅黑樹則直接調(diào)用紅黑樹的遍歷方法 if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); //遍歷鏈表 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //是我們需要的值,返回 return e; } while ((e = e.next) != null); } } return null; }
注意:
1、HashMap底層就是用一個個的Node來存儲單個數(shù)據(jù),每個Node有hash值、key、value、及指向下一個Node的引用(next)。Node數(shù)組中就是所有鏈表的頭節(jié)點(diǎn)。
2、當(dāng)出現(xiàn)hash沖突的情況,原Node的next就會指向新插入的Node,也就是形成了鏈表。
3、每次擴(kuò)容的長度必須是2的冪,因?yàn)?,根?jù)key的hash值計(jì)算出的數(shù)組索引應(yīng)盡量不要重復(fù),實(shí)現(xiàn)均勻分布,均勻分布的話大部分查找的數(shù)據(jù)都是以數(shù)組的形式查找,就不會蛻變成鏈表,而數(shù)組的查找效率比鏈表高很多。
4、影響擴(kuò)容的因素有兩個:數(shù)組的長度(DEFAULT_INITIAL_CAPACITY)和負(fù)載因子(DEFAULT_LOAD_FACTOR),當(dāng)這兩個相乘大于等于當(dāng)前HashMap的Size時,就進(jìn)行擴(kuò)容
5、擴(kuò)容在并發(fā)情況下可能會形成鏈表環(huán),存在并發(fā)安全問題,這點(diǎn)需要注意
6、當(dāng)鏈表的節(jié)點(diǎn)超過8個時,會轉(zhuǎn)成紅黑樹,鏈表的時間復(fù)雜度為O(n),而紅黑樹為O(logn)
到此這篇關(guān)于Java的HashMap源碼解析的文章就介紹到這了,更多相關(guān)HashMap源碼 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot集成validation實(shí)現(xiàn)參數(shù)校驗(yàn)功能
Bean?Validation?是一個運(yùn)行時的數(shù)據(jù)驗(yàn)證框架,在驗(yàn)證之后驗(yàn)證的錯誤信息會被馬上返回,這篇文章主要介紹了Spring?Boot集成validation實(shí)現(xiàn)參數(shù)校驗(yàn)功能,需要的朋友可以參考下2024-05-05Java利用trueLicense實(shí)現(xiàn)項(xiàng)目離線證書授權(quán)操作步驟
文章介紹了如何使用trueLicense實(shí)現(xiàn)離線授權(quán)控制,包括生成公私鑰、創(chuàng)建證書校驗(yàn)?zāi)K、生成證書模塊和測試模塊,通過這種方式,可以控制用戶使用的項(xiàng)目模塊、授權(quán)周期、使用的設(shè)備和服務(wù)器,感興趣的朋友跟隨小編一起看看吧2024-11-11