LinkedHashMap特別有意思,它不僅僅是在HashMap上增加Entry的雙向鏈接,它更能借助此特性實現保證Iterator迭代按照插入順序(以insert模式創建LinkedHashMap)或者實現LRU(Least Recently Used最近最少算法,以access模式創建LinkedHashMap)。
下面是LinkedHashMap的get方法的代碼
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
其中有一段:e.recordAccess(this)。下面我們進入Entry的定義
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
這裏的addBefore(lm.header)是做什麼呢?再看
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
從這裏可以看到了,addBefore(lm.header)是把當前訪問的元素挪到head的前面,即最近訪問的元素被放到了鏈表頭,如此要實現LRU算法只需要從鏈表末尾往前刪除就可以了,多麼巧妙的方法。
在看到LinkedHashMap之前,我以爲實現LRU算法是在每個元素內部維護一個計數器,訪問一次自增一次,計數器最小的會被移除。但是要想到,每次add的時候都需要做這麼一次遍歷循環,並取出最小的拋棄,在HashMap較大的時候效率很差。當然也有其他方法來改進,比如建立<訪問次數,LinkedHashMap元素的key>這樣的TreeMap,在add的時候往TreeMap裏也插入一份,刪除的時候取最小的即可,改進了效率但沒有LinkedHashMap內部的默認實現來的簡捷。
LinkedHashMap是什麼時候刪除的呢?
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
在增加Entry的時候,通過removeEldestEntry(eldest)判斷是否需要刪除最老的Entry,如果需要則remove。注意看這裏Entry<K,V> eldest=header.after,記得我們前面提過LinkedHashMap還維護一個雙向鏈表,這裏的header.after就是鏈表尾部最後一個元素(頭部元素是head.before)。
LinkedHashMap默認的removeEldestEntry方法如下
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
總是返回false,所以開發者需要實現LRU算法只需要繼承LinkedHashMap並重寫removeEldestEntry方法,下面以MyBatis的LRU算法的實現舉例
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
開發者的子類並不需要直接操作eldest(上例中獲得eldestKey只是MyBatis需要映射到Cache對象中的元素),只要根據自己的條件(一般是元素個數是否到達閾值)返回true/false即可。注意,要按照LRU排序必須在new LinkedHashMap()的構造函數的最後一個參數傳入true(true代表LinkedHashMap內部的雙向鏈表按訪問順序排序,false代表按插入順序排序)。
在LinkedHashMap的註釋裏明確提到,該類在保持插入順序、不想HashMap那樣混亂的情況下,又沒有像TreeMap那樣的性能損耗。同時又能夠很巧妙地實現LRU算法。其他方面和HashMap功能一致。有興趣的同學可以仔細看看LinkedHashMap的實現。
LinkedHashMap實現LRU;具體實現如下:只需要重寫removeEldestEntry方法即可
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
public class LRULinkedMap<K, V> {
/**
* 最大緩存大小
*/
private int cacheSize;
private LinkedHashMap<K, V> cacheMap;
public LRULinkedMap(int cacheSize){
this.cacheSize = cacheSize;
cacheMap = new LinkedHashMap(cacheSize, 0.75F, true){
@Override
protected boolean removeEldestEntry(Entry eldest) {
return cacheSize + 1 >= cacheMap.size();
}
};
}
public void put(K key, V value){
cacheMap.put(key, value);
}
public V get(K key){
return cacheMap.get(key);
}
public Collection<Map.Entry<K, V>> getAll(){
return new ArrayList<Map.Entry<K, V>>(cacheMap.entrySet());
}
public static void main(String[] args) {
LRULinkedMap<String, Integer> map = new LRULinkedMap<>(3);
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
for (Map.Entry<String, Integer> e : map.getAll()){
System.out.println(e.getKey()+"====>"+e.getValue());
}
System.out.println("\n");
map.put("key4", 4);
for (Map.Entry<String, Integer> e : map.getAll()){
System.out.println(e.getKey()+"====>"+e.getValue());
}
}
}
原文轉自
https://www.cnblogs.com/mengheng/p/3683137.html