Java实现LRU算法


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与节点的映射关系删除,所以要从链表的节点中获取。


文章作者: Wendell
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wendell !
评论
  目录