日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

LinkedHashMap分析

發(fā)布時(shí)間:2025/3/21 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LinkedHashMap分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

LinkedHashMap分析

這篇文章會(huì)分析一下 LinkedHashMap。

?

LinkedHashMap是啥

首先我們看一下這個(gè)類(lèi)的定義:

1 public class LinkedHashMap<K,V> 2 extends HashMap<K,V> 3 implements Map<K,V>

可以看到LinkedHashMap同樣實(shí)現(xiàn)了Map接口,但它同時(shí)還繼承了HashMap,所以天然就有HashMap自身的特性。

?

從名字上看? LinkedHashMap 比 HashMap 多了前面的“ Linked “, 那它們之間的區(qū)別在哪里呢?我們?cè)倏匆欢未a:

1 /** 2 * LinkedHashMap的Entry對(duì)象,除了繼承于HashMap Node的4個(gè)屬性,額外添加了兩個(gè)屬性before,after用于維護(hù)雙向鏈表的前后結(jié)點(diǎn)關(guān)系 3 */ 4 static class Entry<K,V> extends HashMap.Node<K,V> { 5 Entry<K,V> before, after; 6 Entry(int hash, K key, V value, Node<K,V> next) { 7 super(hash, key, value, next); 8 } 9 }

從這里可以看到相比較于HashMap的Node,除了繼承自HashMap Node數(shù)據(jù)結(jié)構(gòu)中的hash、key、value、next四個(gè)屬性之外,LinkedHashMap的Node還多維護(hù)了兩個(gè)屬性:before,after。

這兩個(gè)屬性是用于維護(hù)一條獨(dú)立的雙向鏈表,而這個(gè)雙向鏈表中保存的僅僅是在LinkedHashMap中節(jié)點(diǎn)與節(jié)點(diǎn)的前后關(guān)系。

所以可以這么認(rèn)為:LinkedHashMap是 "維護(hù)了節(jié)點(diǎn)之間前后順序的HashMap" 。

?

其他用到的屬性:

1 /** 2 * 雙向鏈表的頭結(jié)點(diǎn)(最近最少使用的結(jié)點(diǎn)) 3 */ 4 transient LinkedHashMap.Entry<K,V> head; 5 6 /** 7 * 雙向鏈表的尾結(jié)點(diǎn)(最近最多使用的結(jié)點(diǎn)) 8 */ 9 transient LinkedHashMap.Entry<K,V> tail; 10 11 /** 12 * LinkedHashMap核心屬性,該屬性定義了雙向鏈表中存儲(chǔ)結(jié)點(diǎn)的順序: 13 * 如果為true,則雙向鏈表按照結(jié)點(diǎn)的訪問(wèn)順序維護(hù)前后結(jié)點(diǎn)關(guān)系(訪問(wèn)、操作結(jié)點(diǎn)都會(huì)影響該結(jié)點(diǎn)在雙向鏈表的位置),這種方式實(shí)現(xiàn)了LRU算法 14 * 如果為false,則雙向鏈表僅僅按照結(jié)點(diǎn)的插入順序維護(hù)前后結(jié)點(diǎn)關(guān)系(只有操作結(jié)點(diǎn)的動(dòng)作才會(huì)影響該結(jié)點(diǎn)在雙向鏈表的位置) 15 * 該值默認(rèn)為false. 16 */ 17 final boolean accessOrder;

這里需要著重注意第17行的accessOrder這個(gè)變量,LinkedHashMap通過(guò)在構(gòu)造方法中傳入該參數(shù)的值(true/false)來(lái)控制雙向鏈表中維護(hù)節(jié)點(diǎn)的前后順序時(shí)是根據(jù)訪問(wèn)順序維護(hù)還是插入順序維護(hù)。解釋一下這兩種順序有啥區(qū)別:

?假設(shè)一開(kāi)始往Map中添加了3個(gè)節(jié)點(diǎn)? A、B、C,則此時(shí)雙向鏈表中維護(hù)節(jié)點(diǎn)的前后順序應(yīng)該是? ?A -> B -> C,此時(shí)調(diào)用HashMap的get(key)方法訪問(wèn)B節(jié)點(diǎn):

如果 accessOrder = true, 那么按照訪問(wèn)節(jié)點(diǎn)維護(hù)雙向鏈表中節(jié)點(diǎn)前后順序,此時(shí)節(jié)點(diǎn)的前后順序關(guān)系變更為? ?A -> C -> B;

如果accessOrder = false,那么節(jié)點(diǎn)的前后順序關(guān)系不變,依舊為?A -> B -> C。

當(dāng)然,如果是插入、刪除節(jié)點(diǎn),無(wú)論accessOrder設(shè)置為何值,雙向鏈表中節(jié)點(diǎn)的前后順序關(guān)系都會(huì)發(fā)生改變。比如在Map插入一個(gè)節(jié)點(diǎn)D

那么無(wú)論accessOrder為true還是false,雙向鏈表中節(jié)點(diǎn)的前后順序關(guān)系都會(huì)變更為? A -> B -> C -> D。

?

?

LinkedHashMap核心源碼分析(JDK Version 1.8)

?

1.構(gòu)造方法?

1 /** 2 * 帶capacity和loadFactor的構(gòu)造函數(shù) 3 * 4 * @param initialCapacity 自定義初始容量 5 * @param loadFactor 自定義負(fù)載因子 6 * @throws IllegalArgumentException 初始容量或負(fù)載因子為負(fù)數(shù),拋出異常 7 * 8 */ 9 public LinkedHashMap(int initialCapacity, float loadFactor) { 10 super(initialCapacity, loadFactor); //調(diào)用HashMap的構(gòu)造函數(shù) 11 accessOrder = false; 12 } 13 14 /** 15 * 只帶capacity參數(shù)的構(gòu)造函數(shù),此時(shí)loadFactor為默認(rèn)的0.75 16 * 17 * @param initialCapacity 自定義初始容量 18 * @throws IllegalArgumentException 初始容量為負(fù)數(shù),拋出異常 19 */ 20 public LinkedHashMap(int initialCapacity) { 21 super(initialCapacity); //調(diào)用HashMap的構(gòu)造函數(shù) 22 accessOrder = false; 23 } 24 25 /** 26 * 無(wú)參數(shù)構(gòu)造函數(shù)。初始容量為默認(rèn)的16,負(fù)載因子為默認(rèn)的0.75 27 */ 28 public LinkedHashMap() { 29 super(); //調(diào)用HashMap的構(gòu)造函數(shù) 30 accessOrder = false; 31 } 32 33 34 /** 35 * 三個(gè)參數(shù)的構(gòu)造函數(shù),可以控制雙向鏈表的存儲(chǔ)方式 36 * 37 * @param initialCapacity 初始容量 38 * @param loadFactor 負(fù)載因子 39 * @param accessOrder 雙向鏈表維護(hù)的存儲(chǔ)順序 40 * @throws IllegalArgumentException 初始容量或負(fù)載因子為負(fù)數(shù),拋出異常 41 */ 42 public LinkedHashMap(int initialCapacity, 43 float loadFactor, 44 boolean accessOrder) { 45 super(initialCapacity, loadFactor); //調(diào)用HashMap的構(gòu)造方法 46 this.accessOrder = accessOrder; 47 }


2.LinkedHashMap在訪問(wèn)和操作節(jié)點(diǎn)時(shí)特有的方法

1 /** 2 * LinkedHashMap重寫(xiě)了HashMap的newNode()方法 3 * 該方法在HashMap的putVal()方法中判斷需要新增結(jié)點(diǎn)時(shí)會(huì)被調(diào)用 4 * @param hash 5 * @param key 6 * @param value 7 * @param e 8 * @return 9 */ 10 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { 11 LinkedHashMap.Entry<K,V> p = 12 new LinkedHashMap.Entry<K,V>(hash, key, value, e); //新增一個(gè)key-value的結(jié)點(diǎn) 13 linkNodeLast(p); //LinkedHashMap生成新結(jié)點(diǎn)時(shí)除了新增一個(gè)結(jié)點(diǎn)外,還將該結(jié)點(diǎn)在雙向鏈表中的位置移動(dòng)到尾部,這個(gè)操作默認(rèn)按元素插入順序維護(hù)了鏈表前后結(jié)點(diǎn)關(guān)系 14 return p; 15 } 16 17 18 /** 19 * 將結(jié)點(diǎn)在雙向鏈表中的位置移動(dòng)到尾部 20 * @param p 21 */ 22 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { 23 LinkedHashMap.Entry<K,V> last = tail; //將當(dāng)前雙向鏈表的尾結(jié)點(diǎn)保存下來(lái) 24 tail = p; //尾結(jié)點(diǎn)設(shè)置為傳入的結(jié)點(diǎn) 25 if (last == null) //如果之前鏈表中沒(méi)有結(jié)點(diǎn),即這次新增的結(jié)點(diǎn)是鏈表的頭結(jié)點(diǎn) 26 head = p; 27 else { //如果這次新增的結(jié)點(diǎn)不是鏈表的頭結(jié)點(diǎn),則將其移動(dòng)到鏈表的尾部 28 p.before = last; //當(dāng)前結(jié)點(diǎn)的前驅(qū)指向之前鏈表的尾結(jié)點(diǎn) 29 last.after = p; //之前鏈表尾結(jié)點(diǎn)后驅(qū)指向當(dāng)前結(jié)點(diǎn) 30 } 31 } 32 33 34 /** 35 * 某個(gè)結(jié)點(diǎn)被刪除后的回調(diào)方法 36 * LinkedHashMap在維護(hù)的雙向鏈表中也要把該結(jié)點(diǎn)與前后結(jié)點(diǎn)的關(guān)系移除 37 * @param e 38 */ 39 void afterNodeRemoval(Node<K,V> e) { // unlink 40 LinkedHashMap.Entry<K,V> p = 41 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; 42 p.before = p.after = null; //移除被刪除結(jié)點(diǎn)和前后結(jié)點(diǎn)之前的引用關(guān)系 43 if (b == null) //如果雙向鏈表中被移除的結(jié)點(diǎn)就是首結(jié)點(diǎn) 44 head = a; //將下一個(gè)結(jié)點(diǎn)變?yōu)槭捉Y(jié)點(diǎn) 45 else 46 b.after = a; //否則就把被刪除結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)的后驅(qū)指向被刪除結(jié)點(diǎn)的后一個(gè)結(jié)點(diǎn) 47 if (a == null) //如果雙向鏈表中被移除的結(jié)點(diǎn)就是尾結(jié)點(diǎn) 48 tail = b; //將上一個(gè)結(jié)點(diǎn)變?yōu)槲步Y(jié)點(diǎn) 49 else 50 a.before = b; //否則就把被刪除結(jié)點(diǎn)的后一個(gè)結(jié)點(diǎn)的前驅(qū)指向被刪除結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn) 51 } 52 53 54 /** 55 * 某個(gè)結(jié)點(diǎn)被訪問(wèn)后的回調(diào)方法 56 * 如果在創(chuàng)建LinkedHashMap時(shí)構(gòu)造方法中的accessOrder設(shè)置為true 57 * 則雙向鏈表需要按照元素訪問(wèn)的順序維護(hù)雙向鏈表中結(jié)點(diǎn)的前后引用關(guān)系以實(shí)現(xiàn)LRU算法 58 * 在每次get/put結(jié)點(diǎn)后,都需要調(diào)用這個(gè)回調(diào)方法,將結(jié)點(diǎn)在雙向鏈表中的位置移動(dòng)到尾部(因?yàn)樽罱@個(gè)結(jié)點(diǎn)被訪問(wèn)了) 59 * @param e 60 */ 61 void afterNodeAccess(Node<K,V> e) { // move node to last 62 LinkedHashMap.Entry<K,V> last; 63 if (accessOrder && (last = tail) != e) { //如果指定accessOrder為true并且當(dāng)前訪問(wèn)的結(jié)點(diǎn)在雙向鏈表中不是尾結(jié)點(diǎn)才繼續(xù)做如下操作 64 LinkedHashMap.Entry<K,V> p = 65 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; 66 p.after = null; //將當(dāng)前被訪問(wèn)結(jié)點(diǎn)在雙向鏈表中的后驅(qū)引用設(shè)置為null 67 if (b == null) //如果原先被訪問(wèn)結(jié)點(diǎn)的前驅(qū)引用為null,說(shuō)明這個(gè)被訪問(wèn)結(jié)點(diǎn)原先就是雙向鏈表的頭結(jié)點(diǎn) 68 head = a; //將頭結(jié)點(diǎn)變更為被訪問(wèn)結(jié)點(diǎn)的后一個(gè)結(jié)點(diǎn) 69 else 70 b.after = a; //否則將被訪問(wèn)結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)的后驅(qū)指向被訪問(wèn)結(jié)點(diǎn)的后一個(gè)結(jié)點(diǎn) 71 if (a != null) //如果原先被訪問(wèn)結(jié)點(diǎn)在雙向鏈表中有下一個(gè)結(jié)點(diǎn) 72 a.before = b; //那么將下一個(gè)結(jié)點(diǎn)的前驅(qū)引用指向被訪問(wèn)結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn) 73 else 74 last = b; //否則將尾結(jié)點(diǎn)引用指向設(shè)置為當(dāng)前被訪問(wèn)結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn) 75 if (last == null) //如果原先雙向鏈表中沒(méi)有尾結(jié)點(diǎn),則說(shuō)明此次訪問(wèn)的結(jié)點(diǎn)是該雙向鏈表中第一個(gè)結(jié)點(diǎn) 76 head = p; //將雙向頭結(jié)點(diǎn)設(shè)置為此次被訪問(wèn)的結(jié)點(diǎn) 77 else { //將被訪問(wèn)的結(jié)點(diǎn)移動(dòng)到雙向鏈表的尾部 78 p.before = last; //否則將當(dāng)前結(jié)點(diǎn)的前驅(qū)引用指向原來(lái)雙向鏈表的尾結(jié)點(diǎn) 79 last.after = p; //再將原先雙向鏈表尾結(jié)點(diǎn)的后驅(qū)引用指向當(dāng)前被訪問(wèn)的結(jié)點(diǎn) 80 } 81 tail = p; //雙向鏈表尾結(jié)點(diǎn)變更為當(dāng)前被訪問(wèn)的結(jié)點(diǎn) 82 ++modCount; 83 } 84 }

因?yàn)長(zhǎng)inkedHashMap維護(hù)了雙向鏈表,所以相比HashMap在訪問(wèn)、插入、刪除節(jié)點(diǎn)時(shí)都要額外再對(duì)雙向鏈表中維護(hù)的節(jié)點(diǎn)前后關(guān)系進(jìn)行操作。

結(jié)合注釋看應(yīng)該很好理解,就不再啰嗦了。

?

LinkedHashMap的LRU特性

先講一下LRU的定義:LRU(Least Recently Used),即最近最少使用算法,最初是用于內(nèi)存管理中將無(wú)效的內(nèi)存塊騰出而用于加載數(shù)據(jù)以提高內(nèi)存使用效率而發(fā)明的算法。

目前已經(jīng)普遍用于提高緩存的命中率,如Redis、Memcached中都有使用。

為啥說(shuō)LinkedHashMap本身就實(shí)現(xiàn)了LRU算法?原因就在于它額外維護(hù)的雙向鏈表中。

在上面已經(jīng)提到過(guò),在做get/put操作時(shí),LinkedHashMap會(huì)將當(dāng)前訪問(wèn)/插入的節(jié)點(diǎn)移動(dòng)到鏈表尾部,所以此時(shí)鏈表頭部的那個(gè)節(jié)點(diǎn)就是 "最近最少未被訪問(wèn)"的節(jié)點(diǎn)。

舉個(gè)例子:

往一個(gè)空的LinkedHashMap中插入A、B、C三個(gè)結(jié)點(diǎn),那么鏈表會(huì)經(jīng)歷以下三個(gè)狀態(tài):

1.? A? ?插入A節(jié)點(diǎn),此時(shí)整個(gè)鏈表只有這一個(gè)節(jié)點(diǎn),既是頭節(jié)點(diǎn)也是尾節(jié)點(diǎn)

2.? A? ->? B? ?插入B節(jié)點(diǎn)后,此時(shí)A為頭節(jié)點(diǎn),B為尾節(jié)點(diǎn),而最近最常訪問(wèn)的節(jié)點(diǎn)就是B節(jié)點(diǎn)(剛被插入),而最近最少使用的節(jié)點(diǎn)就是A節(jié)點(diǎn)(相對(duì)B節(jié)點(diǎn)來(lái)講,A節(jié)點(diǎn)已經(jīng)有一段時(shí)間沒(méi)有被訪問(wèn)過(guò))

3.? A? ->? B? ->? C? 插入C節(jié)點(diǎn)后,此時(shí)A為頭節(jié)點(diǎn),C為尾節(jié)點(diǎn),而最近最常訪問(wèn)的節(jié)點(diǎn)就是C節(jié)點(diǎn)(剛被插入),最近最少使用的節(jié)點(diǎn)就是A節(jié)點(diǎn) (應(yīng)該很好理解了吧? : ))

那么對(duì)于緩存來(lái)講,A就是我最長(zhǎng)時(shí)間沒(méi)訪問(wèn)過(guò)的緩存,C就是最近才訪問(wèn)過(guò)的緩存,所以當(dāng)緩存隊(duì)列滿時(shí)就會(huì)從頭開(kāi)始替換新的緩存值進(jìn)去,從而保證緩存隊(duì)列中的緩存盡可能是最近一段時(shí)間訪問(wèn)過(guò)的緩存,提高緩存命中率。

?

OK,到這里L(fēng)inkedHashMap就分析完了,相比于HashMap還是比較容易理解的吧 : )

下一篇文章會(huì)分析 TreeMap 的具體實(shí)現(xiàn)。

?

轉(zhuǎn)載于:https://www.cnblogs.com/smartchen/p/9016176.html

總結(jié)

以上是生活随笔為你收集整理的LinkedHashMap分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。