LRU算法
LRU算法:Latest Recently Used
即我们认为最近访问的数据就是有用的,就应该更新到队头,这样的话,对尾的就应该是很久未被访问的数据。
这样就可以淘汰队尾的数据。
思路
缓存容量:初始化一个缓存容量。
添加数据:判断缓存是否已满?不满,新数据放表头;满,删除表尾数据,新数据放表头。
读取数据:判断数据是否存在?存在,放表头,返回数据;不存在,返回错误。
为了快,添加数据和读取数据的操作应为O(1)。
设计
实现该算法应满足:查找快,插入快,删除快,有顺序之分。
哈希表查找快,但数据无固定顺序;链表有顺序,插入删除快,但查找慢。
所以结合形成一种新的数据结构:哈希链表。

代码
链表节点
/**
 * 链表节点
 */
public class Node {
    Node pre;
    Node next;
    Object val;
    String key;
}
双向链表
public class DoubleLinkedList {
    private int size;
    private Node head = new Node();
    private Node tail = new Node();
    public DoubleLinkedList() {
        head.pre = tail;
        head.next = tail;
        tail.pre = head;
        tail.next = head;
        this.size = 0;
    }
    //添加新节点
    public void addFirst(Node node) {
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
        ++size;
    }
    //移除节点
    public void remove(Node node) {
        if (node != null) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
            --size;
        }
    }
    //移除最后一个数据节点
    public Node removeLast() {
        if (tail.pre != head) {
            Node last = tail.pre;
            remove(last);
            return last;
        }
        return null;
    }
    //获取当前缓存size
    public int getSize() {
        return this.size;
    }
}
LRUCache
public class LRUCache {
    private int capacity;
    private final Map<String, Node> nodeMap;
    private final DoubleLinkedList list;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.nodeMap = new HashMap<>(capacity);
        this.list = new DoubleLinkedList();
    }
    //添加缓存
    public void put(String key, Object val) {
        Node node = new Node();
        node.key = key;
        node.val = val;
        if (nodeMap.containsKey(key)) {
            remove(key);
        } else {
            if (list.getSize() >= capacity) {
                Node last = list.removeLast();
                nodeMap.remove(last.key);
            }
        }
        list.addFirst(node);
        nodeMap.put(key, node);
    }
    //删除缓存
    public void remove(String key) {
        Node node = nodeMap.get(key);
        if (node != null) {
            list.remove(node);
            nodeMap.remove(key);
        }
    }
    //获取缓存
    public Object get(String key) {
        Node node = nodeMap.get(key);
        if (node == null) {
            return null;
        }
        put(node.key, node.val);
        return node.val;
    }
}
测试
public class LRUTest {
    public static void main(String[] args) {
        LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
        cache.put("1", 1);
        cache.put("2", 2);
        System.out.println(cache.get("1"));
        cache.put("3", 3);           // 该操作会使得关键字 2 作废
        System.out.println(cache.get("2"));   // 返回 null (未找到)
        cache.put("4", 4);          // 该操作会使得关键字 1 作废
        System.out.println(cache.get("1"));  // 返回 -1 (未找到)
        System.out.println(cache.get("3"));  // 返回  3
        System.out.println(cache.get("4"));  // 返回  3
    }
}
总结
为什么使用双向链表?
因为在删除节点时,需要获取前置节点,将前置节点的后置节点指向当前节点的后置节点。
如果是单相链表就无法更改。
为什么使用了哈希集合之后,链表中还需要存key?
因为在删除尾节点时,需要获取对应节点的key才可以将哈希中key与节点的映射关系删除,所以要从链表的节点中获取。