前言
HashMap中的元素時無序的,也就是說遍歷HashMap的時候,順序和放入的順序是不一樣的。 如果需要有序的Map,就可以採用LinkedHashMap.
LinkedHashMap通過維護一個包含所有元素的雙向鏈表,保證了元素迭代的順序。該迭代順序可以是插入順序或者是訪問順序。
LinkedHashMap
注意: 以下的源碼分析基於jdk1.7.
LinkedHashMap定義
LinkedHashMap 繼承了HashMap, 也就是說LinkedHashMap的操作大部分和HashMap是相同的,只是有部分區別。
LinkedHashMap的屬性相比於HashMap,多了兩個屬性:
header: 雙端鏈表的頭節點;(LinkedHashMap通過這個頭節點來維護插入元素的先後順序)
accessOrder: 迭代Map的順序.true表示訪問順序,false表示插入順序;默認是false。
LinkedHashMap.Entry
相比於hashMap.Entry多了兩個屬性:before,after
.
before、After是用於維護Entry在雙端鏈表中的位置的。
LinkedHashMap的初始化
當new 一個LinkedHashMap,查看其構造方法(調用了父類HashMap的構造方法),構造方法中執行了LinkedHashMap重寫的init方法:
init方法中初始化了雙端鏈表的頭節點。
LinkedHashMap的put方法
下面跟蹤put方法,來看LinkedHashMap的插入邏輯。 首先執行父類HashMap的put()方法。
再看LinkedHashMap的addEntry方法:
先調用了父類HashMap的addEntry方法,進入HashMap的addEntry方法:
再進入LinkedHashMap的createEntry方法:
因此,如果連續插入三個entry,那麼LinkedHashMap中的雙端鏈表結構如下:
再看inkedHashMap的addEntry方法的後半段代碼:
LinkedHashMap的get方法
查看LinkedHashMap的get方法:
這裏面會調用LinkedHashMap.Entry的recordAccess方法,目的是根據acceessOrder(訪問順序) 來調整鏈表的位置。
調整鏈表的圖示:
可見,最新被訪問的元素,被放到了雙端鏈表的最後面,也就是說,遍歷的時候,這個元素會最後遍歷。
LinkedHashMap的遍歷方式
查看對應的Iterator類的nextEntry方法:
可知,LinkedHashMap遍歷時是從head.after開始遍歷,一直不斷向後遍歷after節點,來實現的。
總結
LinkedHashMap的遍歷方式
1.按照插入順序遍歷;
2.按照最少訪問順序遍歷。(也就是說剛被訪問的元素被插入到鏈表最後面,最後遍歷出來,而最老的元素最先遍歷出來)
利用LinkedHashMap實現LRU算法
LRU算法
LRU(Least Recently Used),即最近最少使用的意思。
LRU算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。
利用LinkedHashMap實現LRU算法
前面的源碼分析中,在put元素的時候,LinkedHashMap中有一個removeEldestEntry方法,支持擴展,按照某種方式,在插入的時候,去移除最老的key, 因此根據LinkedHashMap的這個特性,可以實現LRU算法。 實現方式如下:
/**
* 實現一個簡單版的LRU算法
*
* @author leiqian
*
*/
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE; // 緩存大小
public LRUCache(int cacheSize) {
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > CACHE_SIZE; // 當map中的數據量大於指定的緩存個數的時候,就自動刪除最老的數據
}
}
測試:
/**
* 測試LRUCache
*
* @author leiqian
*
*/
public class LRUCacheTest {
public static void print(Map<String, String> map) {
System.out.println("-----------------------");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "," + entry.getValue());
}
}
public static void main(String[] args) {
LRUCache<String, String> map = new LRUCache<>(5);
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
map.put("k4", "v4");
map.put("k5", "v5");
map.put("k6", "v6");
print(map); // 應該移除了k1
map.get("k2");
map.put("k7", "v7");
print(map); // 應該移除了k3
map.get("k4");
map.put("k8", "v8");
print(map); // 應該移除了k5
}
}
另外,很多優秀的第三方框架都實現了LRUCache,可以看一下他們的源碼中是怎麼實現LRUCache的: