LRU算法和LFU算法

文章详细介绍了LRU(最近最少使用)和LFU(最近最不频繁使用)两种缓存淘汰策略,包括它们的基本实现、优缺点以及Redis中的应用。Redis通过优化LRU算法,使用对象结构体记录访问时间,避免维护大型链表,同时LFU算法通过访问频次和时间戳管理数据,以解决缓存污染问题。
摘要由CSDN通过智能技术生成

LRU(Least Recently Used)最近最少使用,淘汰最近最少使用的数据,

LFU(Least Frequently Used)最近最不频繁用,淘汰最不常用的数据。

LRU算法

传统的LRU基于链表实现。基本的一个LRU算法实现就是【Hash表+双向链表】。

算法执行:

        按最近使用顺序排序,被操作的数据元素,会拆链表,把该数据元素移到最前面,合链表。

        链表尾部代表最久未使用的数据元素。

操作两个问题:

        ① 链表存储所有数据带来开销;

        ② 大量数据访问时,过多的解链表、合链表会严重影响性能。

LFU算法

核心思想是:默认访问多的数据越重要。

解决偶尔访问一次数据就会存留较长时间的情况,会比较符合应用场景。但实现起来较复杂,基本的一个LFU算法实现需要【两个Hash表+多个双向链表】。不过两者的Hash表都是用来存储key-节点的关系。不同在于LFU需要多一个Hash去记录在哪一个双向链表。

执行:

  1. key不存在返回null;

  2. key存在,返回对应节点的value值。同时,需将节点的访问次数+1,然后将该节点从原双向链表中移除,添加到新的访问次数对应的双向链表中。若hash表不存在对应的双向链表则需要先创建链表。(访问次数与双向链表数对应)

        Redis实现

Redis的内存淘汰策略中,就有LRU和LFU算法的实现,不完全相同但类似。Redis3.0之前使用的内存淘汰策略为:volatile-lru(基于设置过期时间数据进行淘汰的LRU算法)

Redis实现LRU算法:

        是在Redis的对象结构体中添加一额外字段,记录最新一次的访问时间。

当Redis进行内存淘汰时, 会使用随机采样的方式来淘汰数据,淘汰访问时间距离最近的数据。

优点:

        相比传统LRU算法,省内存空间,不必去维护一个较大占空间的链表。

        相比传统LRU算法,省去链表数据移到操作,提高性能。

缺点:

        缓存污染问题:一次读取大量数据后,这些数据会留在数据库较长时间。这也是引入LFU算法

        的目的之一。

Redis实现LFU算法:

        是在Redis对象头中存放 ldt (占16bit)和 logc (占8bit)分别记录key的最新访问时间戳和key的访问频次。 当为什么这里需要时间呢,不是论次数吗?因为这里的 logc 不是记录单纯的访问次数,而是频次,会随时间衰减,随访问次数增加而增长,因此有 ldt 存在的必要性。

ps:Redis中是有两个参数lfu-decay-time和lfu-log-factor分别用于设置logc的衰减和增长速度。

回想起Leetcode上一道经典的算法题LRU(纯思路题,并不涉及复杂算法):

力扣https://leetcode.cn/problems/lru-cache/附上题解:

class LRUCache {
    class LinkList {
        int key;
        int value;
        LinkList prev;
        LinkList next;
        public LinkList() {}
        public LinkList(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    private int size;
    private int capacity;
    private HashMap<Integer, LinkList> map = new HashMap<>();
    private LinkList head;   // 头指针
    private LinkList tail;   // 尾指针

    public LRUCache(int capacity) {
        this.capacity = capacity;
        size = 0;
        head = new LinkList();
        tail = new LinkList();
        head.next = tail;
        tail.prev = head;  // 头尾互指易漏
    }
    
    public int get(int key) {
        LinkList node = map.get(key);
        if (node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        LinkList node = map.get(key);
        if (node == null) {
            if (size++ == capacity) {
                LinkList tailNode = removeTail();
                map.remove(tailNode.key);
                size--;  // 必须有size--,不然其实一直在size++
            }
            node = new LinkList(key, value);  // 因为下面同一个了,所以这里必须更新node
            addHead(node);
            map.put(key, node);
        } else {
            node.value = value;  // 更新value值
        }
        moveToHead(node);
    }

    void moveToHead(LinkList node) {
        removeNode(node);
        addHead(node);
    }

    void removeNode(LinkList node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    void addHead(LinkList node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    LinkList removeTail() {  
        LinkList tailNode = tail.prev;
        removeNode(tailNode);
        return tailNode;
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值