图解LRU算法

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图解LRU算法相关的知识,希望对你有一定的参考价值。

一、什么是LRU算法?

           LRU 全称Least Recently Used, LRU算法是一种操作系统层面上的页面置换算法,将最近最久未使用的页面进行淘汰。

二、基于双向链表+Map实现LRU算法

         为什么选用双向链表做cache?

          思想:    将多次访问的节点移到头节点,将少访问的节点移到尾节点。

          由于双向链表的自身特性,它能够很方便地找到上一个节点和下一个节点,对节点的操作相对来说比较容易。单向的不能找到上一个节点,在操作时不能提供当前节点的上一个节点遍历。

        1.  用双向链表看成cache缓存, 数据存放在链表上的每个节点上。

             在使用cache时,主要有2种操作,一种是向cache里添加数据, 另一种是从cache里获取数据。

             对于get操作: 

                    1) 如果map 里不存在,那么直接返回null。

                    2) 如果map里存在,找到将该节点移到到双向链表的表头。

            对于put操作:

                   1)  先判断用上述的get操作获取到node, 如果node不存在,接着通过 history.size()==capcity 判断容量是否已满,如果满了,就将尾节点从map 中移除,同时清除掉双向链表中的尾节点。

                  2) 如果node存在,那么就cache(双向链表) 中的对应的node移到链表头。

       2.  用Map记录访问cache的历史, 只要访问了 cache就将节点放置Map里。

       3.  图解移动节点和淘汰策略过程

                只要画一下图,我们就会发现双向链表的优点体现出来了。

                        

 三、完整代码

package sort;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author bingbing
 * @date 2021/5/7 0007 9:58
 */
public class LRUCache<V> {


    private int capacity = 1024;


    private static Map<String, Node> history = new ConcurrentHashMap<>();

    Node head;
    Node tail;

    public LRUCache(int capacity) {
        this();
        this.capacity = capacity;
    }

    public LRUCache() {
        head = new Node();
        tail = new Node();
        head.next = tail;
        head.pre = null;
        tail.pre = head;
        tail.next = null;
    }

    private V get(String key) {
        Node node = history.get(key);
        if (null == node) {
            return null;
        }
        // 将节点移动表头
        node.pre.next = node.next;
        node.next.pre = node.pre;
        node.next=head.next;
        head.next.pre=node;
        head.next=node;
        node.pre=head;
        return (V) node;
    }

    private void put(String key, V value) {
        Node node = history.get(key);
        if (null == node) {
            // 判断容量是否已满
            if (history.size() == capacity) {
                history.remove(tail.pre.key);
                tail.pre = tail.next;
                tail.next = null;
                tail = tail.pre;
            }
        }
        // 插入到表头
        node = (Node) value;
        history.put(key, node);
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
    }

    static class Node<k, v> {

        private k key;

        private v value;

        Node<k, v> pre;

        Node<k, v> next;

        public Node(k key, v value) {
            this.key = key;
            this.value = value;
        }

        public Node() {

        }
    }

    public static void main(String[] args) {
        LRUCache<Node> cache = new LRUCache<>(4);
        Node<String, Integer> node1 = new Node<String, Integer>("k1", 1);
        Node<String, Integer> node2 = new Node<String, Integer>("k2", 2);
        Node<String, Integer> node3 = new Node<String, Integer>("k3", 3);
        Node<String, Integer> node4 = new Node<String, Integer>("k4", 4);
        Node<String, Integer> node5 = new Node<String, Integer>("k5", 5);
        cache.put("k1", node1);
        cache.put("k2", node2);
        cache.put("k3", node3);
        cache.put("k4", node4);
//        cache.get("k1");
        cache.put("k5", node5);
        Node node = cache.get("k1");
        System.out.println(node);
    }

}

效果演示:

       1) 注释掉 cache.get("k1"), 从cache中拿k1, 查看结果

null

      2)  放开  cache.get("k1"), 从cache中拿k1, 查看结果

sort.LRUCache$Node@14ae5a5

第一次的容量只有4,按照最少使用规则,那么就会淘汰k1, 因此第一次测试结果为null, 第二次测试因为重新获取了k1, k1被移到了头节点,淘汰的是k2, 因此能够拿到k1。

参考: 

 https://blog.csdn.net/belongtocode/article/details/102989685

 https://blog.csdn.net/qq_26440803/article/details/83795122

以上是关于图解LRU算法的主要内容,如果未能解决你的问题,请参考以下文章

Python性能提升神器!lru_cache的介绍和讲解

面试不再怕,20行Python代码帮你搞懂LRU算法

页面置换算法LRU

页面置换算法LRU

LRU算法

Goland 实现LRU算法