一、LRU算法简介

LRU(Least Recently Used)最近最久未使用算法

常见应用场景:内存管理中的页面置换算法、缓存淘汰中的淘汰策略等

 

二、实现理论

  底层结构:双向链表 + HashMap ,双向链表由特定的哈希节点组成。

(1)访问节点时,将其从原来位置删除,插入到双向链表头部;

(2)更新节点时,先删除原有缓存数据(即原有节点),然后更新map映射,再将更新值作为节点插入链表头;更新后,判断容量是否超过最大内存使用量

(3)超过则执行淘汰;淘汰即删除双向链表最后一个节点,同时删除map中的映射

(4)LRU实现中有频繁的查找节点并删除,为节省时间(链表查找目标节点需要遍历),使用HashMap保存键-节点映射关系,O(1)的查找+O(1)的删除

(5)LRU实现中,要频繁的在头部插入,以及在尾部删除;因此,需要定义head、tail两个节点,方便操作

 

三、代码 

package cache;

import java.util.HashMap;

/**
 * 底层结构:双向链表 + HashMap ,双向链表由特定的哈希节点组成。
 * <p>
 * (1)访问节点时,将其从原来位置删除,插入到双向链表头部;
 * (2)更新节点时,先删除原有缓存数据(即原有节点),然后更新map映射,再将更新值作为节点插入链表头;更新后,判断容量是否超过最大内存使用量
 * (3)超过则执行淘汰;淘汰即删除双向链表最后一个节点,同时删除map中的映射
 * (4)LRU实现中有频繁的查找节点并删除,为节省时间(链表查找目标节点需要遍历),使用HashMap保存键-节点映射关系,O(1)的查找+O(1)的删除
 * (5)LRU实现中,要频繁的在头部插入,以及在尾部删除;因此,需要定义head、tail两个节点,方便操作
 */
public class LRUCache<K, V> {

    private class Node {
        K key;
        V val;
        Node pre;
        Node next;

        public Node(K k, V v) {
            this.key = k;
            this.val = v;
        }
    }

    private Node head;
    private Node tail;
    private HashMap<K, Node> map;
    private int maxSize;

    public LRUCache(int maxSize) {
        this.maxSize = maxSize;
        this.map = new HashMap<>((int) (maxSize / 0.75));
        this.head.next = tail;
        this.tail.pre = head;
    }

    //获取指定数据
    public V get(K k) {
        //判断是否存在对应数据
        if (!map.containsKey(k)) {
            return null;
        }
        //先从链表原来位置中删除,然后添加到添加到链表头部
        Node node = map.get(k);
        removeNode(node);
        addFirst(node);
        return node.val;
    }

    public void put(K k, V v) {
        if (map.containsKey(k)) {
            Node node = map.get(k);
            //先删除原有节点
            removeNode(node);
        }
        Node now = new Node(k, v);
        //更新内存映射
        map.put(k, now);
        //插入链表头
        addFirst(now);
        //判断是否需要淘汰数据
        if (map.size() > maxSize) {
            removeLast();
            //同时删除map中的映射
            map.remove(k);
        }
    }

    public void removeNode(Node node) {
        Node pre = node.pre;
        Node next = node.next;
        pre.next = next;
        next.pre = pre;
        node.pre = null;
        node.next = null;
    }

    public void addFirst(Node node) {
        Node next = head.next;
        head.next = node;
        node.pre = head;
        node.next = next;
        next.pre = node;
    }

    public Node removeLast() {
        Node last = tail.pre;
        removeNode(last);
        return last;
    }


}