手写LRU算法

一、LRU是什么

LRU算法是根据对数据的历史访问次数来进行淘汰数据的,通常使用双向链表来实现。在缓存中,它的设计原则是:如果数据最近被访问过,那么将来它被访问的几率也很高。

二、具体实现方式

利用双向链表与散列表的结合:

  1. 双向链表支持查找前驱,保证操作的时间复杂度为O(1)
  2. 引入散列表记录每个数据的位置,将缓存访问的时间复杂度降到O(1)
/**
 * 查询位置的数据节点
 *
 * @author slp
 */
public class Node<K, V> {

    public K key;

    public V value;

    public Node<K, V> pre;

    public Node<K, V> next;

    public Node() {
    }

    public Node(K key, V value) {
        this.key = key;
        this.value = value;
    }
}
/**
 * 存储数据节点的双向链表
 *
 * @author slp
 */
public class DoubleLinkedList<K, V> {

    public Node<K, V> head;

    public Node<K, V> tail;

    public LruCache cache;

    public DoubleLinkedList(LruCache cache) {
        this.head = new Node<>();
        this.tail = new Node<>();
        // 尾部指向头部的下一个节点
        this.head.next = tail;
        // 头部指向尾部的前驱节点
        this.tail.pre = head;
        // 初始化缓存数据节点信息
        this.cache = cache;
    }

    /**
     * 获取最后一个数据节点
     *
     * @return
     */
    private Node<K, V> getLastNode() {
        // 获取最后一个节点指向尾部的前驱节点
        return this.tail.pre;
    }

    /**
     * 添加一个新节点
     */
    private void addNode(Node<K, V> node) {
        // tail 1 2 3 4 5 head
        // tail 1 2 3 4 5 ^6 head
        node.next = head.next;
        node.pre = head;
        head.next.pre = node;
        head.next = node;
    }

    /**
     * 移除一个数据节点
     *
     * @param node
     */
    private void removeNode(Node<K, V> node) {
        node.next.pre = node.pre;
        node.pre.next = node.next;
        node.pre = null;
        node.next = null;
    }

    /**
     * 获取数据节点
     *
     * @param key
     * @return
     */
    public int getNode(int key) {
        // 判断是否存在 key 数据节点
        if (cache.map.containsKey(key)) {
            Node<Integer, Integer> node = cache.map.get(key);
            // 存在先删除当前数据节点,再重新添加该节点到链表头部
            cache.list.removeNode(node);
            cache.list.addNode(node);
            return node.value;
        }
        // 不存在则返回 -1
        return -1;
    }

    /**
     * 存储一个数据节点
     *
     * @param key
     * @param value
     */
    public void putNode(int key, int value) {
        // 判断缓存数据 key 是否存在
        if (cache.map.containsKey(key)) {
            Node<Integer, Integer> node = cache.map.get(key);
            // 存在则覆盖原值
            node.value = value;
            // 移动该 key 到链表的头部
            cache.list.removeNode(node);
            cache.list.addNode(node);
        } else {
            // 不存在则创建一个数据节点
            Node<Integer, Integer> node = new Node<>(key, value);
            // 判断维护的 map 缓存数量是否达到极限
            if (cache.map.size() == cache.capacity) {
                // 移除链表中的尾节点,将新节点添加到链表头部
                Node<Integer, Integer> lastNode = cache.list.getLastNode();
                cache.map.remove(lastNode.key);
                cache.map.put(key, node);
                cache.list.removeNode(lastNode);
                cache.list.addNode(node);
            } else {
                // 直接添加到链表头部节点
                cache.map.put(key, node);
                cache.list.addNode(node);
            }
        }
    }
}

/**
 * 实现 LRU 算法缓存
 *
 * @author slp
 */
public class LruCache {

    // 缓存容量
    public final int capacity;

    // 维护查找数据节点位置 map 集合
    public final Map<Integer, Node<Integer, Integer>> map;

    // 双向链表存储数据节点集合
    public final DoubleLinkedList<Integer, Integer> list;

    public LruCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.list = new DoubleLinkedList<>(this);
    }
}

三、实验测试代码

/**
 * 测试 LRU 缓存
 */
public class TestLruDemo {

    public static void main(String[] args) {

        LruCache cache = new LruCache(3);
        DoubleLinkedList<Integer, Integer> list = cache.list;
        list.putNode(1, 1);
        list.putNode(2, 2);
        list.putNode(3, 3);
        print(list.head);

        /**
         * 3 3
         * 2 2
         * 1 1
         */

        list.putNode(4, 4);
        print(list.head);

        /**
         * 4 4
         * 3 3
         * 2 2
         */

        list.putNode(5, 5);
        print(list.head);

        /**
         * 5 5
         * 4 4
         * 3 3
         */

    }

    public static void print(Node<Integer, Integer> head) {
        Node<Integer, Integer> node = head.next;
        System.out.println("-----------------");
        while (node != null && node.next != null) {
            System.out.println("key:" + node.key + "---value:" + node.value);
            node = node.next;
        }
    }
}

你可能感兴趣的:(Java,算法,链表,java)