数据库
首页 > 数据库> > Redis缓存淘汰算法——LRU、LFU

Redis缓存淘汰算法——LRU、LFU

作者:互联网

个人博客欢迎访问

总结不易,如果对你有帮助,请点赞关注支持一下
微信搜索程序dunk,关注公众号,获取博客源码、数据结构与算法笔记(超级全)、大厂面试、笔试题

Redis过期键的删除策略

对于过期键一般的三种删除策略

三种策略优缺点

问题来了:定期没删,也没查询,该怎么办?

内存淘汰机制

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

LRU(最近最少使用算法)

/**
 * @author :zsy
 * @date :Created 2021/7/25 12:41
 * @description:https://www.nowcoder.com/practice/e3769a5f49894d49b871c09cadd13a61?tpId=190&&tqId=35214&rp=1&ru=/activity/oj&qru=/ta/job-code-high-rd/question-ranking
 */
public class NC93设计LRU缓存结构 {
    @Test
    public void test() {
    }


    public class Solution {
        /**
         * lru design
         * @param operators int整型二维数组 the ops
         * @param k int整型 the k
         * @return int整型一维数组
         */
        public int[] LRU (int[][] operators, int k) {
            // write code here
            LRUCache lruCache = new LRUCache(k);
            ArrayList<Integer> res = new ArrayList<>();
            for (int[] operator : operators) {
                if (operator[0] == 1) {
                    lruCache.put(operator[1], operator[2]);
                } else {
                    res.add(lruCache.get(operator[1]));
                }
            }
            int[] ans = new int[res.size()];
            for (int i = 0; i < res.size(); i++) {
                ans[i] = res.get(i);
            }
            return ans;
        }

        class LRUCache {
            int capacity;
            Map<Integer, Node> map;
            DoubleList list;

            public LRUCache (int cap) {
                this.capacity = cap;
                this.map = new HashMap<>();
                this.list = new DoubleList();
            }

            /**
             * 添加缓存
             * 如果map中包含当前key,则修改当前的val值,并将节点添加到链表头部
             * 否则
             *  如果双端链表的长度小于cap,直接添加到链表头部
             *  否则,移除链表最后一个节点,添加到链表头部,删除map中的映射
             *  最后,添加到map中
             * @param key
             * @param val
             */
            public void put(int key, int val) {
                Node cur = new Node(key, val);
                if(map.containsKey(key)) {
                    list.remove(map.get(key));
                    list.addFirst(cur);
                } else {
                    if(list.size == capacity) {
                        Node node = list.removeLast();
                        map.remove(node.key);
                    }
                    list.addFirst(cur);
                }
                map.put(key, cur);
            }

            /**
             * 如果map中不包含key,返回-1
             * 否则,获取当前节点,删除当前节点(map、list),将节点添加到list头部
             * @param key
             * @return
             */
            public int get(int key) {
                if(!map.containsKey(key)) return -1;
                Node target = map.get(key);
                list.remove(target);
                list.addFirst(target);
                return target.val;
            }
        }

        class Node {
            int key, val;
            Node next, pre;

            public Node(int key, int val) {
                this.key = key;
                this.val = val;
            }

            @Override
            public String toString() {
                return "Node{" +
                        "key=" + key +
                        ", val=" + val +
                        '}';
            }
        }

        class DoubleList {
            Node head;
            Node tail;
            int size;

            public DoubleList() {
                head = new Node(-1, -1);
                tail = new Node(-1, -1);
                head.next = tail;
                tail.pre = head;
                size = 0;
            }

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


            public Node remove(Node node) {
                node.pre.next = node.next;
                node.next.pre = node.pre;
                size--;
                return node;
            }

            public Node removeLast() {
                Node target = tail.pre;
                tail.pre.pre.next = tail;
                tail.pre = tail.pre.pre;
                size--;
                return target;
            }

            public void list() {
                Node temp = head.next;
                while(temp != tail) {
                    System.out.println(temp);
                    temp = temp.next;
                }
            }

        }
    }
}

找一个数据结构实现下Java版本的LRU还是比较容易的,知道啥原理就好了

使用LinkedHashMap版的LRU算法

/**
 * @author :zsy
 * @date :Created 2021/5/14 11:23
 * @description:使用LinkedHashMap构建LRU页面置换算法
 */
public class LRULinkedHashMap {

    public static void main(String[] args) {
        LRUCache<Integer, Integer> lruCache = new LRUCache<>(3);
        lruCache.put(1,2);
        lruCache.put(2,2);
        lruCache.put(3,2);
        lruCache.get(2);
        //必须这样测试
        //否则会报ConcurrentModificationException异常
        Iterator<Map.Entry<Integer, Integer>> it = lruCache.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = it.next();
            System.out.println(entry.getKey() + "   " + entry.getValue());
        }
        lruCache.put(4,3);
        lruCache.get(2);
        lruCache.put(4,5);
        it = lruCache.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = it.next();
            System.out.println(entry.getKey() + "   " + entry.getValue());
        }
    }

    static class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int CACHE_SIZE;

        public LRUCache(int cacheSize) {
            //true表示让hashmap按照访问顺序,最新访问的放在头部,最老访问的放在尾部
            super((int)Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true);
            this.CACHE_SIZE = cacheSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            //当map中数量大于指定缓存数量,就删除最老的数据
            return size() > CACHE_SIZE;
        }
    }

}

LFU(最不经常使用算法)

但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;

如果调用次数最少的key有多个,上次调用发生最早的key被删除

/**
 * @author :zsy
 * @date :Created 2021/7/25 13:31
 * @description:https://www.nowcoder.com/practice/93aacb4a887b46d897b00823f30bfea1?tpId=190&&tqId=35215&rp=1&ru=/activity/oj&qru=/ta/job-code-high-rd/question-ranking
 */
public class NC94LFU缓存结构设计 {
    @Test
    public void test() {
        Solution solution = new Solution();
    }

    public class Solution {
        /**
         * lfu design
         * @param operators int整型二维数组 ops
         * @param k int整型 the k
         * @return int整型一维数组
         */
        public int[] LFU (int[][] operators, int k) {
            // write code here
            LFUCache lfuCache = new LFUCache(k);
            ArrayList<Integer> res = new ArrayList<>();
            for (int[] operator : operators) {
                if (operator[0] == 1) {
                    lfuCache.put(operator[1], operator[2]);
                } else {
                    res.add(lfuCache.get(operator[1]));
                }
            }
            int[] ans = new int[res.size()];
            for (int i = 0; i < res.size(); i++) {
                ans[i] = res.get(i);
            }
            return ans;
        }

        class LFUCache {
            //容量
            int capacity;
            //map
            Map<Integer, Node> map;
            //双向链表
            DoubleList list;

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

            /**
             *  1、已经存在key
             *      -覆盖val,frequency++
             *      -删除链表中的节点,重新加入
             *  2、不存在
             *      -达到了最大容量
             *          删除链表第一个元素,执行插入操作
             *      -未达到
             *          直接插入节点
             * @param key
             * @param val
             */
            public void put(int key, int val) {
                if (map.containsKey(key)) {
                    Node node = map.get(key);
                    list.remove(node);
                    node.val = val;
                    node.frequency++;
                    map.put(key, node);
                    list.add(node);
                } else {
                    Node cur = new Node(key, val, 1);
                    if (list.size >= capacity) {
                        Node node = list.removeFirst();
                        map.remove(node.key);
                    }
                    list.add(cur);
                    map.put(key, cur);
                }
            }

            /**
             * 获取val,frequency++,链表重新赋值
             * @param key
             * @return
             */
            public int get(int key) {
                if (!map.containsKey(key)) return -1;
                Node node = map.get(key);
                list.remove(node);
                node.frequency++;
                list.add(node);
                return node.val;
            }
        }

        //双向链表
        class DoubleList {
            //头尾指针
            Node head, tail;
            //节点数量
            int size;

            public DoubleList() {
                this.head = new Node(-1, -1, -1);
                this.tail = new Node(-1, -1, Integer.MAX_VALUE);
                head.next = tail;
                tail.pre = head;
                size = 0;
            }

            /**
             * 删除节点
             * @param node
             * @return
             */
            private Node remove(Node node) {
                node.pre.next = node.next;
                node.next.pre = node.pre;
                size--;
                return node;
            }

            /**
             * 添加节点,并且维护链表顺序
             *  -频次升序
             *  -相同频次,按操作时间早->晚排序(方便删除)
             * @param node
             */
            private void add(Node node) {
                Node temp = head;
                while (node.frequency >= temp.next.frequency) {
                    temp = temp.next;
                }
                node.next = temp.next;
                temp.next.pre = node;
                temp.next = node;
                node.pre = temp;
                size++;
            }

            /**
             * 删除第一个节点,即调用次数最少,且调用发生最早
             * @return
             */
            private Node removeFirst() {
                Node tar = head.next;
                head.next = tar.next;
                tar.next.pre = head;
                size--;
                return tar;
            }

            /**
             * 展示列表,测试使用
             */
            private void list() {
                Node temp = head.next;
                while (temp != null && temp != tail) {
                    System.out.println(temp);
                    temp = temp.next;
                }
            }
        }

        //Node存放K-V值和当前节点操作的频次
        class Node {
            private int key, val, frequency;
            Node pre, next;

            public Node(int key, int val, int frequency) {
                this.key = key;
                this.val = val;
                this.frequency = frequency;
            }
        }
    }
}

标签:node,Node,int,Redis,next,LFU,LRU,key,public
来源: https://blog.csdn.net/qq_45796208/article/details/119082357