如何使用LinkedHashMap來實現一個LruCache

最近在看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;
      }
    };
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章