最近在看mybatis的源代碼,發現了mybatis中實現的LruCache使用到了LinkedHashMap,所以就探究了一下LinkedHashMap是如何支持Lru緩存的
LinkedHashMap內部維護了一個所有的Entity的雙向鏈表
同時構造方法可以設置Iterator的時候,是按照插入的順序排序還是按照訪問的順序排序
默認是按照插入的順序來排序的,在構造方法里邊可以設置按照訪問的順序來排序
那究竟按照訪問的順序來排序是什么意思呢?
LinkedHashMap的get(key)方法是自己實現的,並沒有從HashMap里邊繼承,我們看看get(Key)方法的實現是什么樣子的
我們看afterNodeAccess()方法是如何實現的
這個方法主要就是移動雙向鏈表的指針,將傳入的結點移動到LinkedHashMap維護的雙向鏈表的末尾,這樣每次通過get(key)方法訪問一個元素,這個元素就會被移動到雙向鏈表的末尾,按照訪問的順序來排序,就是每次通過Iterator來遍歷keySet或者是EntrySet的時候,訪問過的元素會出現在最后邊(因為LinedHashMap的Iterator遍歷的時候,遍歷的是內部的雙向鏈表,從頭結點,遍歷到尾結點)
順着這樣的思路,如果在滿足一定條件的情況下,移除掉雙向鏈表的頭結點,這樣就實現了一個LruCahe
其實LinkedHashMap已經為我們提供了這樣的方法,LinkedHashMap中有一個方法removeEldestEntry(entry) 我們只需要覆蓋這個方法,根據我們自己的需求在一定條件下返回true,這樣就實現了LruCache
改方法的默認實現是返回false
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
LinkedHashMap的afterNodeInsertion()方法會根據其他條件以及removeEldestEntry的返回值來決定是否刪除到雙向鏈表的表頭元素
依據此,我們使用LinkedHashMap來實現一個最簡單的Lru緩存如下:
import org.junit.Test;
import java.util.LinkedHashMap;
import java.util.Map;
public class TestCache {
@Test
public void testLinkedHashMap() {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(5, 0.75F, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
//當LinkHashMap的容量大於等於5的時候,再插入就移除舊的元素
return this.size() >= 5;
}
};
map.put("aa", "bb");
map.put("cc", "dd");
map.put("ee", "ff");
map.put("gg", "hh");
print(map);
map.get("cc");
System.out.println("===================================");
print(map);
map.get("ee");
map.get("aa");
System.out.println("====================================");
map.put("ss","oo");
print(map);
}
void print(LinkedHashMap<String, String> source) {
source.keySet().iterator().forEachRemaining(System.out::println);
}
}
Mybatis中的Lrucache實現也是類似的思路,比較簡單,下邊是關鍵的代碼:
構造方法中調用了setSize()方法,默認緩存1024個元素
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
setSize()方法中初始化了HashMap,並實現了removeEldestEntry()方法
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}