摘要:的相比的最大的變動(dòng)就是結(jié)構(gòu)的修改,在之前數(shù)組鏈表的基礎(chǔ)上,增加了紅黑樹的結(jié)構(gòu)。如下圖下面我們就來(lái)一起學(xué)習(xí)一下的源碼吧這里我想先吐槽一下的源碼,代碼可讀性比差太多了腦殼疼但是在精簡(jiǎn)程度上要比的好一些。
jdk1.8的hashMap相比1.7的最大的變動(dòng)就是結(jié)構(gòu)的修改,在之前數(shù)組+鏈表的基礎(chǔ)上,增加了紅黑樹的結(jié)構(gòu)。
1.7的hashMap我們已經(jīng)看過(guò)了,其中在查找節(jié)點(diǎn)的時(shí)候,會(huì)去根據(jù)hash找到對(duì)應(yīng)的數(shù)組,接著去遍歷之后的鏈表結(jié)構(gòu),當(dāng)hash沖突比較多的時(shí)候,鏈表就會(huì)非常的長(zhǎng),此時(shí)遍歷鏈表的效率就會(huì)很低,所以大神們?cè)趯⒓t黑樹加入到了1.8的hashMap中,當(dāng)鏈表長(zhǎng)度大于8的時(shí)候,會(huì)將鏈表轉(zhuǎn)換為紅黑樹,提高了查找節(jié)點(diǎn)的效率。如下圖:
下面我們就來(lái)一起學(xué)習(xí)一下jdk1.8的hashMap源碼吧!
這里我想先吐槽一下1.8的源碼,代碼可讀性比1.7差太多了...腦殼疼~但是在精簡(jiǎn)程度上要比1.7的好一些。
先看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) { // 這里p可以視為一個(gè)指針,指向tab[i]位置的節(jié)點(diǎn) // n: 數(shù)組的length // i: 根據(jù)hash算出的數(shù)組下標(biāo) Node[] tab; Node p; int n, i; // 將tab數(shù)組指向table,并判斷table如果為空,則進(jìn)入resize()中進(jìn)行初始化 if ((tab = table) == null || (n = tab.length) == 0) // 1.8的hashMap將初始化方法和resize()合并到了一起 n = (tab = resize()).length; // 很據(jù)hash值找到tab[i]并將p指向tabl[i],如果沒(méi)有內(nèi)容,創(chuàng)建新的鏈表節(jié)點(diǎn)放到i的位置 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, else { // 進(jìn)入else表示tabl[i]處有內(nèi)容,下面需要進(jìn)一步判斷key是否一致 Node e; K k; // 插入的key和tab[i]處的key相等,將p賦值給e(exist)節(jié)點(diǎn) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果該節(jié)點(diǎn)是代表紅黑樹的節(jié)點(diǎn),調(diào)用紅黑樹的插值方法 else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); else { // 進(jìn)入else說(shuō)明hash相同,且tab[i]處是一個(gè)多節(jié)點(diǎn)的鏈表 // 循環(huán)鏈表 for (int binCount = 0; ; ++binCount) { // 將e指向p.next,并判斷p.next是否有內(nèi)容, // 如果沒(méi)有內(nèi)容,說(shuō)明tab[i]處沒(méi)找到一致的key,將會(huì)此節(jié)點(diǎn)作為新節(jié)點(diǎn)插入 // 插入的位置為鏈表尾部 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 鏈表節(jié)點(diǎn)數(shù)如果大于8,調(diào)用treeifyBin將鏈表轉(zhuǎn)為紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for treeifyBin(tab, hash); break; } // 在tab[i]中找到了相同的key,跳出循環(huán) // 此時(shí)e指向tab[i]中key等于新插入key的鏈表節(jié)點(diǎn) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 插入的key在鏈表中已存在,只需要直接覆蓋即可 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 判斷是否需要擴(kuò)容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
還需要注意的是
1.8中的put方法是在鏈表結(jié)尾插入新節(jié)點(diǎn),而1.7是在頭部插入新節(jié)點(diǎn)
1.8是先插入,再擴(kuò)容,1.7是先擴(kuò)容,再插入
接下來(lái)看一下resize()擴(kuò)容方法
final Node[] resize() { Node [] oldTab = table; // 當(dāng)前容量 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 當(dāng)前閾值 int oldThr = threshold; int newCap, newThr = 0; // 當(dāng)前容量>0,表示map中已有內(nèi)容 if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 擴(kuò)容一倍,并將閾值×2 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // 首次put,oldThr > 0 表示使用的是`new HashMap(int initialCapacity)`構(gòu)造器進(jìn)行的初始化 else if (oldThr > 0) // initial capacity was placed in threshold // 初始化大小=閾值 newCap = oldThr; // 首次put,else 表示使用的是默認(rèn)構(gòu)造器`new HashMap()`進(jìn)行的初始化 else { // zero initial threshold signifies using defaults // 初始化大小=默認(rèn)大小(16)并計(jì)算閾值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } 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"}) // 創(chuàng)建新數(shù)組 Node [] newTab = (Node [])new Node[newCap]; table = newTab; // 舊數(shù)據(jù)遷移 if (oldTab != null) { // 遍歷舊數(shù)組 for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; // oldTab[j]處只有一個(gè)節(jié)點(diǎn),就不需要遍歷鏈表了,直接將此節(jié)點(diǎn)賦值到新數(shù)組對(duì)應(yīng)hash位置上 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 處理紅黑樹節(jié)點(diǎn) else if (e instanceof TreeNode) ((TreeNode )e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; // e.hash & oldCap 將舊的鏈表分成了lo(e.hash & oldCap為偶數(shù))和hi(e.hash & oldCap為奇數(shù))兩個(gè)鏈 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // lo鏈會(huì)分配到和原下標(biāo)相同的位置 // hi鏈會(huì)被分配到原下標(biāo)+oldCap的位置 if (loTail != null) { loTail.next = null; // lo鏈 newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; // hi鏈 newTab[j + oldCap] = hiHead; } } } } } return newTab; }
get() 方法
相對(duì)put方法,get就簡(jiǎn)單了許多
final NodegetNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 如果第一個(gè)節(jié)點(diǎn)就是需要的,直接返回 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { // 從紅黑樹中取節(jié)點(diǎn) if (first instanceof TreeNode) return ((TreeNode )first).getTreeNode(hash, key); // 從鏈表中取節(jié)點(diǎn) do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
remove() 刪除節(jié)點(diǎn)
final NoderemoveNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node [] tab; Node p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { // node: 要被刪除的節(jié)點(diǎn) Node node = null, e; K k; V v; // 如果頭節(jié)點(diǎn)匹配,直接將node指向頭節(jié)點(diǎn) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { // 如果頭節(jié)點(diǎn)不匹配,且頭節(jié)點(diǎn)屬于紅黑樹節(jié)點(diǎn),從樹中取出要?jiǎng)h除的節(jié)點(diǎn) if (p instanceof TreeNode) node = ((TreeNode )p).getTreeNode(hash, key); // 如果頭節(jié)點(diǎn)不匹配,且頭節(jié)點(diǎn)屬于鏈表節(jié)點(diǎn),遍歷鏈表取出要?jiǎng)h除的節(jié)點(diǎn) else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } //如果找到了符合條件的待刪除節(jié)點(diǎn),根據(jù)節(jié)點(diǎn)類型去紅黑樹中或者鏈表中刪除指定節(jié)點(diǎn) if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode )node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
總結(jié)一下:
相對(duì)1.7的hashmap不同,1.8的結(jié)構(gòu)采用數(shù)組+鏈表+紅黑樹的結(jié)構(gòu)
1.8中的put方法是在鏈表結(jié)尾插入新節(jié)點(diǎn),而1.7是在頭部插入新節(jié)點(diǎn)
1.8是先插入,再擴(kuò)容,1.7是先擴(kuò)容,再插入
至此,1.8的hashMap源碼閱讀到這里就告一段落了,后邊我們會(huì)繼續(xù)看一下不同版本的ConcurrentHashMap源碼,歡迎觀看~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75466.html
摘要:所謂拉鏈法就是將鏈表和數(shù)組相結(jié)合。若遇到哈希沖突,則將沖突的值加到鏈表中即可。在編寫程序中,要盡量避免。 目錄: 0-1. 簡(jiǎn)介 0-2. 內(nèi)部結(jié)構(gòu)分析 0-2-1. JDK18之前 0-2-2. JDK18之后 0-3. LinkedList源碼分析 0-3-1. 構(gòu)造方法 0-3-2. put方法 0-3-3. get方法 0-3-4. resize方法 ...
摘要:值得位數(shù)有的次方,如果直接拿散列值作為下標(biāo)訪問(wèn)主數(shù)組的話,只要算法比較均勻,一般是很難出現(xiàn)碰撞的。但是內(nèi)存裝不下這么大的數(shù)組,所以計(jì)算數(shù)組下標(biāo)就采取了一種折中的辦法,就是將得到的散列值與數(shù)組長(zhǎng)度做一個(gè)與操作。 hashMap簡(jiǎn)單介紹 hashMap是面試中的高頻考點(diǎn),或許日常工作中我們只需把hashMap給new出來(lái),調(diào)用put和get方法就完了。但是hashMap給我們提供了一個(gè)絕佳...
摘要:唐老師,回答道讀源碼是要建立在你的基礎(chǔ)經(jīng)驗(yàn)足夠的情況下。除了自己去閱讀源碼之外,比如學(xué)習(xí)某個(gè)類的時(shí)候,可以專門結(jié)合一些優(yōu)質(zhì)的博客針對(duì)性的對(duì)比學(xué)習(xí),并查漏補(bǔ)缺。制定源碼學(xué)習(xí)計(jì)劃。多調(diào)試,跟蹤源碼。如若有好的學(xué)習(xí)方法,可以留言一起交流學(xué)習(xí)。 序言:目前看一看源碼,來(lái)提升自己的技術(shù)實(shí)力。同時(shí)現(xiàn)在好多面試官都喜歡問(wèn)源碼,問(wèn)你是否讀過(guò)JDK源碼等等? 針對(duì)如何閱讀源碼,也請(qǐng)教了我的老師。下面就先...
摘要:注意排版不需要花花綠綠的,盡量使用語(yǔ)法。協(xié)議的長(zhǎng)連接和短連接,實(shí)質(zhì)上是協(xié)議的長(zhǎng)連接和短連接。長(zhǎng)連接短連接究竟是什么三次握手和四次揮手面試??蜑榱藴?zhǔn)確無(wú)誤地把數(shù)據(jù)送達(dá)目標(biāo)處,協(xié)議采用了三次握手策略。 一 簡(jiǎn)歷該如何寫 1.1 為什么說(shuō)簡(jiǎn)歷很重要?1.2-這3點(diǎn)你必須知道1.3-兩大法則了解一1.4-項(xiàng)目經(jīng)歷怎么寫?1.5-專業(yè)技能該怎么寫?1.6-開源程序員簡(jiǎn)歷模板分享1.7 其他的一些...
閱讀 774·2019-08-29 12:49
閱讀 3562·2019-08-29 11:32
閱讀 3456·2019-08-26 10:43
閱讀 2412·2019-08-23 16:53
閱讀 2060·2019-08-23 15:56
閱讀 1706·2019-08-23 12:03
閱讀 2778·2019-08-23 11:25
閱讀 2092·2019-08-22 15:11