前言
聲明,本文用得是jdk1.8
前面已經講了Collection的總覽和剖析List集合以及散列表、Map集合、紅黑樹還有HashMap基礎了:
本篇主要講解LinkedHashMap~
看這篇文章之前最好是有點數據結構的基礎:
當然了,如果講得有錯的地方還請大家多多包涵並不吝在評論去指正~
一、LinkedHashMap剖析
LinkedHashMap數據結構圖:
ps:圖片來源網絡,侵刪~
首先我們來看看類繼承圖:
我簡單翻譯了一下頂部的注釋(我英文水平渣,如果有錯的地方請多多包涵~歡迎在評論區下指正)
從頂部翻譯我們就可以歸納總結出HashMap幾點:
- 底層是散列表和雙向鏈表
- 允許為null,不同步
- 插入的順序是有序的(底層鏈表致使有序)
- 裝載因子和初始容量對LinkedHashMap影響是很大的~
同時也給我帶了幾個疑問:
- access-ordered和insertion-ordered具體的使用和意思
- 為什么說初始容量對遍歷沒有影響?
希望可以在看源碼的過程中可以解決掉我這兩個疑問~那接下來就開始吧~
1.1LinkedHashMap的域
1.2LinkedHashMap重寫的方法
下面我列舉就這兩個比較重要的:
這就印證了我們的LinkedHashMap底層確確實實是散列表和雙向鏈表~
- 在構建新節點時,構建的是
LinkedHashMap.Entry
不再是Node
.
1.3構造方法
可以發現,LinkedHashMap有5個構造方法:
下面我們來看看構造方法的定義是怎么樣的:
從構造方法上我們可以知道的是:LinkedHashMap默認使用的是插入順序
1.4put方法
原本我是想要找put方法,看看是怎么實現的,后來沒找着,就奇了個怪~
再頓了一下,原來LinkedHashMap和HashMap的put方法是一樣的!LinkedHashMap繼承着HashMap,LinkedHashMap沒有重寫HashMap的put方法
所以,LinkedHashMap的put方法和HashMap是一樣的。
如果沒看過HashMap就是這么簡單【源碼剖析】的同學,可進去看看~
當然了,在創建節點的時候,調用的是LinkedHashMap重寫的方法~
1.5get方法
get方法也是多了:判斷是否為訪問順序~~~
講到了這里,感覺我們可以簡單測試一波了:
首先我們來看看已插入順序來進行插入和遍歷:
public static void insertOrder() {
// 默認是插入順序
LinkedHashMap<Integer,String> insertOrder = new LinkedHashMap();
String value = "關注公眾號Java3y";
int i = 0;
insertOrder.put(i++, value);
insertOrder.put(i++, value);
insertOrder.put(i++, value);
insertOrder.put(i++, value);
insertOrder.put(i++, value);
//遍歷
Set<Integer> set = insertOrder.keySet();
for (Integer s : set) {
String mapValue = insertOrder.get(s);
System.out.println(s + "---" + mapValue);
}
}
測試一波:
接着,我們來測試一下以訪問順序來進行插入和遍歷:
public static void accessOrder() {
// 設置為訪問順序的方式
LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);
String value = "關注公眾號Java3y";
int i = 0;
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
// 遍歷
Set<Integer> sets = accessOrder.keySet();
for (Integer key : sets) {
String mapValue = accessOrder.get(key);
System.out.println(key + "---" + mapValue);
}
}
代碼看似是沒有問題,但是運行會出錯的!
前面在看源碼注釋的時候我們就發現了:在AccessOrder的情況下,使用get方法也是結構性的修改!
為了簡單看出他倆的區別,下面我就直接用key來進行看了~
以下是訪問順序的測試:
public static void accessOrder() {
// 設置為訪問順序的方式
LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);
String value = "關注公眾號Java3y";
int i = 0;
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
accessOrder.put(i++, value);
// 訪問一下key為3的元素再進行遍歷
accessOrder.get(3);
// 遍歷
Set<Integer> sets = accessOrder.keySet();
for (Integer key : sets) {
System.out.println(key );
}
}
測試結果:
以下是插入順序的測試(代碼就不貼了,和上面幾乎一樣):
我們可以這樣理解:最常用的將其放在鏈表的最后,不常用的放在鏈表的最前~
這個知識點以我的理解而言,它這個訪問順序在LinkedHashMap如果不重寫用處並不大~它是用來給別的實現進行擴展的
- 因為最常被使用的元素再遍歷的時候卻放在了最后邊,在LinkedHashMap中我也沒找到對應的方法來進行調用~
- 一個
removeEldestEntry(Map.Entry<K,V> eldest)
方法,重寫它可以刪除最久未被使用的元素!! - 還有一個是
afterNodeInsertion(boolean evict)
方法,新增時判斷是否需要刪除最久未被使用的元素!!
去網上搜了幾篇資料,都是講LRUMap的實現的(也就是對LinkedHashMap進行擴展),有興趣的同學可參考下列鏈接:
- https://blog.csdn.net/exceptional_derek/article/details/11713255
- http://www.php.cn/java-article-362041.html
- https://www.jianshu.com/p/1a66529e1a2e
- https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ%3D%3D&chksm=ebd639d5dca1b0c3ba5a26bd46d265544f4fdd468df6465e54d93da230c3457d4947e79eaf0c&idx=1&mid=2247485177&sn=93cfa2c2e6f3e5092e5850bdb5ea4cc3
1.6remove方法
對於remove方法,在LinkedHashMap中也沒有重寫,它調用的還是父類的HashMap的remove()
方法,在LinkedHashMap中重寫的是:afterNodeRemoval(Node<K,V> e)
這個方法
當然了,在remove的時候會涉及到上面重寫的方法:
1.7遍歷的方法
Set<Map.Entry<K,V>> entrySet()
是被重寫的了
看到了這里,我們就知道為啥注釋說:初始容量對遍歷沒有影響
因為它遍歷的是LinkedHashMap內部維護的一個雙向鏈表,而不是散列表(當然了,鏈表雙向鏈表的元素都來源於散列表)
二、總結
LinkedHashMap比HashMap多了一個雙向鏈表的維護,在數據結構而言它要復雜一些,閱讀源碼起來比較輕松一些,因為大多都由HashMap實現了..
閱讀源碼的時候我們會發現多態是無處不在的~子類用父類的方法,子類重寫了父類的部分方法即可達到不一樣的效果!
- 比如:LinkedHashMap並沒有重寫put方法,而put方法內部的
newNode()
方法重寫了。LinkedHashMap調用父類的put方法,里面回調的是重寫后的newNode()
,從而達到目的!
LinkedHashMap可以設置兩種遍歷順序:
- 訪問順序(access-ordered)
- 插入順序(insertion-ordered)
- 默認是插入順序的
對於訪問順序,它是LRU(最近最少使用)算法的實現,要使用它要么重寫LinkedListMap的幾個方法(removeEldestEntry(Map.Entry<K,V> eldest)
和afterNodeInsertion(boolean evict)
),要么是擴展成LRUMap來使用,不然設置為訪問順序(access-ordered)的用處不大~
LinkedHashMap遍歷的是內部維護的雙向鏈表,所以說初始容量對LinkedHashMap遍歷是不受影響的
參考資料:
- 《Core Java》
- https://blog.csdn.net/zxt0601/article/details/77429150
- https://blog.csdn.net/panweiwei1994/article/details/76555359
- https://zhuanlan.zhihu.com/p/28216267
- https://blog.csdn.net/fan2012huan/article/details/51097331
- https://www.cnblogs.com/chinajava/p/5808416.html
明天要是無意外的話,可能會寫TreeMap,敬請期待哦~~~~
文章的目錄導航:https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友