用LinkedHashMap实现最简单的LRU算法

79 阅读2分钟

LRU (Least recently used:最近最少使用)算法

先上代码

public class LRUCache extends LinkedHashMap<Object,Object> {
    private static final long serialVersionUID = 1L;


    // 容纳的最大数量,是我们缓存的最大数量,也是触发回收的临界点
    Integer cacheSize;

    public LRUCache(Integer cacheSize) {
        super(cacheSize, 0.75f, true);
        this.cacheSize = cacheSize;
    }


    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > cacheSize;
    }


}

测试一下

public static void main(String[] args) {
    LRUCache lruCache = new LRUCache(3);
    lruCache.put("1", "1");
    lruCache.put("2", "2");
    lruCache.put("3", "3");
    lruCache.put("4", "4");
    System.out.println(lruCache);
    
}

image.png

代码很简单,我这边是基于1.8 的版本,可以看到我的最大容量设置的是3,当塞入第四个参数的时候,第一个值被删除了,而LinkedHashMap 是按照插入顺序存储的,我们可以猜测会优先移除头数据,看看代码是不是这样

代码分析

首先我们重写了构造方法,父类的构造方法最重要的就是设置accessOrder 属性为 true 然后重写removeEldestEntry()方法, 这个在插入元素后告诉程序改执行删除操作了,

首先我们看下put()方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        .....
        // 这个方法判断是否需要删除
        afterNodeInsertion(evict);
        return null;
    }
    .....
    // 最后到这个afterNodeInsertion方法中,我们可以看到备注上写着是不是需要把老的移除
    // 重写的removeEldestEntry方法就在这个了
    // 我们可以看到,这边移除的是头元素,猜测验证了
void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    

再看下get()方法,那么按照道理,它应该是将访问的元素放到了最后边

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
            // 这个accessOrder就是我们的构造防范传入的第三个参数,会触发访问元素移动
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
   
 }
 // 这个方法,会将刚才访问的元素,移动到尾部
 void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

removeEldestEntry()方法是判断是否需要启动回收的方法,在一些业务中可以还得进行资源回收,像mysql 的连接jar包中也有一个LRUCache,其中一个是存储ServerPreparedStatement对象,删除的时候需要关闭连接

image.png