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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

linkedHashMap源码解析(JDK1.8)

發(fā)布時(shí)間:2024/6/30 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linkedHashMap源码解析(JDK1.8) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言

關(guān)于java中的不常見模塊,讓我一下子想我也想不出來,所以我希望以后每次遇到的時(shí)候我就加一篇。上次有人建議我寫全所有常用的Map,所以我研究了一晚上LinkedHashMap,把自己感悟到的解釋給大家。在本篇博文中,我會用一個(gè)例子展現(xiàn)LinkedHashMap的運(yùn)行和初始化情況,展示LinkedHashMap的數(shù)據(jù)存儲情況,同時(shí)用JDK1.8中它的源代碼解釋給大家。其實(shí),在以前的博文中我就已經(jīng)粗略的介紹過了,關(guān)于緩存機(jī)制的實(shí)現(xiàn):http://blog.csdn.net/u012403290/article/details/68926201中就涉及到了LinkedHashMap,不過今天我們會從源碼解釋它為什么可以實(shí)現(xiàn)LRU(最近最少使用)緩存。最后,筆者目前整理的一些blog針對面試都是超高頻出現(xiàn)的。大家可以點(diǎn)擊鏈接:http://blog.csdn.net/u012403290。

注意

我希望看這篇的文章的小伙伴如果沒有了解過HashMap那么可以先看看我這篇文章:http://blog.csdn.net/u012403290/article/details/65442646,在這篇文章中我詳細(xì)介紹了HashMap的底層實(shí)現(xiàn)和一些常見的成員變量。只有在對HashMap有一定的了解之后,才能很好的理解LinkedHashMap,因?yàn)樗抢^承HashMap實(shí)現(xiàn)的。所以對于加載因子,容量,桶的概念就不再贅述。

數(shù)據(jù)存儲結(jié)構(gòu)

我們已經(jīng)知道HashMap是以散列表的形式存儲數(shù)據(jù)的,LinkedHashMap繼承了HashMap,所以LinkedHashMap其實(shí)也是散列表的結(jié)構(gòu),但是“l(fā)inked”是它對HashMap功能的進(jìn)一步增強(qiáng),LinkedHashMap用雙向鏈表的結(jié)構(gòu),把所有存儲在HashMap中的數(shù)據(jù)連接起來。有人會說散列表不是已經(jīng)有了鏈表的存儲結(jié)構(gòu)了嘛,為什么還要來個(gè)雙向鏈表?桶(桶的概念就是數(shù)組的一個(gè)存儲節(jié)點(diǎn),比如說arr[0]是一個(gè)桶,arr[1]也是一個(gè)桶)中的鏈表和這個(gè)雙向鏈表是兩個(gè)概念,以下是我總結(jié)的區(qū)別:①桶中的鏈表是散列表結(jié)構(gòu)的一部分;而雙向鏈表是LinkedHashMap的額外引入;②桶中的鏈表只做數(shù)據(jù)存儲,沒有存儲順序的概念;雙向鏈表的核心就是控制數(shù)據(jù)存儲順序(存儲順序是LinkedHashMap的核心);③桶中的鏈表產(chǎn)生是因?yàn)榘l(fā)生了hash碰撞,導(dǎo)致數(shù)據(jù)散落在一個(gè)桶中,用鏈表給予存儲,所以這個(gè)鏈表控制了一個(gè)桶;雙向鏈表是要串連所有的數(shù)據(jù),也就是說有桶中的數(shù)據(jù)都是會被這個(gè)雙向鏈表管理。

所以,我修改了HashMap的圖片,大家參考下:?
?
所以,簡單來說就是LinkedHashMap相比于HashMap來說就是多了這些紅色的雙向鏈表而已。

兩種演示

LinkedHashMap的核心就是存在存儲順序和可以實(shí)現(xiàn)LRU算法,所以下面我會用兩個(gè)demo先來證明這兩種情況:?
①、放入到LinkedHashMap是有順序的,會按照你放入的順序存儲:

package com.brickworkers; import java.util.LinkedHashMap;/*** @author Brickworker* Date:2017年4月12日下午12:46:25 * 關(guān)于類LinkedHashMapTest.java的描述:jdk1.8逐字逐句帶你理解linkedHashMap* Copyright (c) 2017, brcikworker All Rights Reserved.*/ public class LinkedHashMapTest { public static void main(String[] args) { LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(); for (int i = 0; i < 10; i++) {//按順序放入1~9 map.put(i, i); } System.out.println("原數(shù)據(jù):"+map.toString()); map.get(3); System.out.println("查詢存在的某一個(gè):"+map.toString()); map.put(4, 4); System.out.println("插入已存在的某一個(gè):"+map.toString()); //直接調(diào)用已存在的toString方法,不然自己需要用迭代器實(shí)現(xiàn) map.put(10, 10); System.out.println("插入一個(gè)原本沒存在的:"+map.toString()); } //輸出結(jié)果 // 原數(shù)據(jù):{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9} // 查詢存在的某一個(gè):{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9} // 插入已存在的某一個(gè):{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9} // 插入一個(gè)原本沒存在的:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10} }

?

觀察以上代碼,其實(shí)它是符合先進(jìn)先出的規(guī)則的,不管你怎么查詢插入已存在的數(shù)據(jù),不會對排序造成影響,如果有新插入的數(shù)據(jù)將會放在最尾部。

②了解,啟用LRU規(guī)則的LinkedHashMap,啟動(dòng)這個(gè)規(guī)則需要在構(gòu)造LinkedHashMap的時(shí)候,調(diào)用三個(gè)參數(shù)的構(gòu)造器,這個(gè)構(gòu)造器源碼如下:

/*** Constructs an empty <tt>LinkedHashMap</tt> instance with the* specified initial capacity, load factor and ordering mode.** @param initialCapacity the initial capacity* @param loadFactor the load factor* @param accessOrder the ordering mode - <tt>true</tt> for* access-order, <tt>false</tt> for insertion-order* @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder;//是否開啟LRU規(guī)則 }

?

第三個(gè)參數(shù)accessOrder就是用于控制LRU規(guī)則的。?
如下就是我寫的demo:

package com.brickworkers;import java.util.LinkedHashMap;/*** @author Brickworker* Date:2017年4月12日下午12:46:25 * 關(guān)于類LinkedHashMapTest.java的描述:jdk1.8逐字逐句帶你理解linkedHashMap* Copyright (c) 2017, brcikworker All Rights Reserved.*/ public class LinkedHashMapTest { public static void main(String[] args) { LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(20, 0.75f, true); for (int i = 0; i < 10; i++) {//按順序放入1~9 map.put(i, i); } System.out.println("原數(shù)據(jù):"+map.toString()); map.get(3); System.out.println("查詢存在的某一個(gè):"+map.toString()); map.put(4, 4); System.out.println("插入已存在的某一個(gè):"+map.toString()); //直接調(diào)用已存在的toString方法,不然自己需要用迭代器實(shí)現(xiàn) map.put(10, 10); System.out.println("插入一個(gè)原本沒存在的:"+map.toString()); } //輸出結(jié)果 // 原數(shù)據(jù):{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9} // 查詢存在的某一個(gè):{0=0, 1=1, 2=2, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 3=3} //被訪問(get)的3放到了最后面 // 插入已存在的某一個(gè):{0=0, 1=1, 2=2, 5=5, 6=6, 7=7, 8=8, 9=9, 3=3, 4=4}//被訪問(put)的4放到了最后面 // 插入一個(gè)原本沒存在的:{0=0, 1=1, 2=2, 5=5, 6=6, 7=7, 8=8, 9=9, 3=3, 4=4, 10=10}//新增一個(gè)放到最后面 }

從上面可以看出,每當(dāng)我get或者put一個(gè)已存在的數(shù)據(jù),就會把這個(gè)數(shù)據(jù)放到雙向鏈表的尾部,put一個(gè)新的數(shù)據(jù)也會放到雙向鏈表的尾部。

逐字逐句底層源碼

接下來我們通過源碼深入學(xué)習(xí)LinkedHash,同時(shí)解答上述出現(xiàn)的有趣的事情。

①分析LinkedHashMap的類名和繼承關(guān)系:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {

?

從這里我們可以看出LinkedHashMap是繼承了HashMap并實(shí)現(xiàn)了Map接口的,所以它和HashMap的關(guān)系肯定不一般。

②分析LinkedHashMap的構(gòu)造函數(shù):

//1public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } //2 public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } //3 public LinkedHashMap() { super(); accessOrder = false; } //4 public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } //5 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }

?

?

它具有5個(gè)構(gòu)造函數(shù),可以設(shè)置容量和加載因子,且在默認(rèn)情況下是不開啟LRU規(guī)則的。同時(shí)它還以用具有繼承K,V關(guān)系的map進(jìn)行初始化。

③分析雙向鏈表

/*** HashMap.Node subclass for normal LinkedHashMap entries.*/static class Entry<K,V> extends HashMap.Node<K,V> {//前后指針Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next); } } /** * The head (eldest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> head;//雙向鏈表頭節(jié)點(diǎn)(最老) /** * The tail (youngest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> tail;//雙向列表尾節(jié)點(diǎn)(最新)

?

?

用一個(gè)靜態(tài)內(nèi)部類Entry表示雙向鏈表,實(shí)現(xiàn)了HashMap中的Node內(nèi)部類。before和after表示前后指針。我們在使用LinkedHashMap有序就是因此產(chǎn)生。

④分析LRU規(guī)則實(shí)現(xiàn),最近訪問放在雙向鏈表最后面

void afterNodeAccess(Node<K,V> e) { // 把當(dāng)前節(jié)點(diǎn)e放到雙向鏈表尾部LinkedHashMap.Entry<K,V> last;//accessOrder就是我們前面說的LRU控制,當(dāng)它為true,同時(shí)e對象不是尾節(jié)點(diǎn)(如果訪問尾節(jié)點(diǎn)就不需要設(shè)置,該方法就是把節(jié)點(diǎn)放置到尾節(jié)點(diǎn))if (accessOrder && (last = tail) != e) {//用a和b分別記錄該節(jié)點(diǎn)前面和后面的節(jié)點(diǎn) LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //釋放當(dāng)前節(jié)點(diǎn)與后節(jié)點(diǎn)的關(guān)系 p.after = null; //如果當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)是空, if (b == null) //那么頭節(jié)點(diǎn)就設(shè)置為a head = a; else //如果b不為null,那么b的后節(jié)點(diǎn)指向a b.after = a; //如果a節(jié)點(diǎn)不為空 if (a != null) //a的后節(jié)點(diǎn)指向b a.before = b; else //如果a為空,那么b就是尾節(jié)點(diǎn) last = b; //如果尾節(jié)點(diǎn)為空 if (last == null) //那么p為頭節(jié)點(diǎn) head = p; else { //否則就把p放到雙向鏈表最尾處 p.before = last; last.after = p; } //設(shè)置尾節(jié)點(diǎn)為P tail = p; //LinkedHashMap對象操作次數(shù)+1 ++modCount; } }

?

?

afterNodeAccess方法就是如何支持LRU規(guī)則的,如果在accessOrder為true的時(shí)候,節(jié)點(diǎn)調(diào)用這個(gè)函數(shù),就會把該節(jié)點(diǎn)放置到最后面。put,get等都會調(diào)用這個(gè)函數(shù)來調(diào)整順序,我手畫了一張圖來表示這個(gè)函數(shù)干了些什么:?

我們看看get方法中是否調(diào)用了此函數(shù),以下是LinkedHashMap重寫了HashMap的get方法:

public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null; if (accessOrder)//如果啟用了LRU規(guī)則 afterNodeAccess(e);//那么把該節(jié)點(diǎn)移到雙向鏈表最后面 return e.value; }

?

?

那么有些小伙伴就問了,那么put方法里面調(diào)用了嘛?肯定調(diào)用了,但是你不一定找得到,因?yàn)長inkedHashMap壓根沒有重寫put方法,每次用LinkedHashMap的put方法的時(shí)候,其實(shí)你調(diào)用的都是HashMap的put方法。那為什么它也會執(zhí)行afterNodeAccess()方法呢,因?yàn)檫@個(gè)方法HashMap就是存在的,但是沒有實(shí)現(xiàn),LinkedHashMap重寫了afterNodeAccess()這個(gè)方法。下面給出HashMap的put局部方法:

if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e);//把當(dāng)前節(jié)點(diǎn)移動(dòng)到雙向鏈表最尾處 return oldValue; }

?

?

其實(shí)這個(gè)方法在很多的調(diào)用都有,這里就不一一解釋了。同時(shí),LinkedHashMap對于紅黑樹的節(jié)點(diǎn)移動(dòng)處理也有專門的方法,這里就不再深入講解了,方式也是差不多。

⑤分析一個(gè)LinkedHashMap自帶的移除最頭(最老)數(shù)據(jù)的方法

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false; }

LinkedHashMap有一個(gè)自帶的移除最老數(shù)據(jù)的方法,但是這個(gè)方法永遠(yuǎn)是返回false,但是如果我們要實(shí)現(xiàn),可以在繼承的時(shí)候重寫這個(gè)方法,給定一個(gè)條件就可以控制存儲在LinkedHashMap中的最老數(shù)據(jù)何時(shí)刪除。具體的在我以前博文多種方式實(shí)現(xiàn)緩存機(jī)制中有寫過。觸發(fā)這個(gè)刪除機(jī)制,一般是在PUT一個(gè)數(shù)據(jù)進(jìn)入的時(shí)候,但是LinkedHashMap并沒有重寫Put方法如何實(shí)現(xiàn)呢?在LinekdHashMap中,這個(gè)方法被包含在afterNodeInsertion()方法之中,而這個(gè)方法是重寫了HashMap的,但是HashMap中并沒有去實(shí)現(xiàn)它,所以在put的時(shí)候就會觸發(fā)刪除這個(gè)機(jī)制。

尾記

技術(shù)是不斷前進(jìn)的,或許在JDK1.8的時(shí)候我寫的這些是有用的,但是下一個(gè)版本出來就不一定了。比如說前面幾個(gè)版本中,LinkedHashMap是一個(gè)循環(huán)的雙向鏈表,而且需要用init()方法進(jìn)行初始化,但是后來都被移除了,以下是部分原本的linkedHashMap源碼:

void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; //循環(huán)的雙向鏈表 }

?

?

所以,在日常的學(xué)習(xí)中,尤其是技術(shù),要與時(shí)俱進(jìn),在查詢某個(gè)技術(shù)點(diǎn)的時(shí)候,千萬要注意版本號,不一樣的版本之間可能是天差地別的。

?

轉(zhuǎn)載:https://blog.csdn.net/u012403290/article/details/70143443?locationNum=14&fps=1

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

總結(jié)

以上是生活随笔為你收集整理的linkedHashMap源码解析(JDK1.8)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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