首页 > 其他分享 >LFU缓存

LFU缓存

时间:2023-09-25 13:23:39浏览次数:36  
标签:node dummy 缓存 链表 LFU key freq 节点

一. 使用两个哈希实现

一个哈希进行直接索引,另一个哈希根据访问频率索引双向链表

/*
定义Node类 双链表节点,包含键、值、前驱、后继

定义LFUCache 类
变量
min_freq:当前最小频率层次
capacity:容量
key_to_node:根据键值索引节点的哈希
freq_to_dummy:根据频率索引双链表的哈希

方法
get_node:根据键获取并更新节点
new_list :创建一个新的双链表头结点
push_front:在对应频率的双链表上插入节点
remove :在链表结构中删除该节点,如果链表为空,删除该链表
release : 释放出一个内存

get: 根据键获取对应值
put: 插入键值对
*/

class Node { //双向链表节点,同时要存储键和值
public:
    int key, value, freq = 1; 
    Node *prev, *next;
    Node(int k = 0, int v = 0) : key(k), value(v) {}
};

class LFUCache {
private:
    int min_freq;//记录最小一层的频率,用于移除释放
    int capacity;
    unordered_map<int, Node*> key_to_node;  //一个哈希直接找对应节点
    unordered_map<int, Node*> freq_to_dummy; //一个哈希找对应频率的双链表

    Node *get_node(int key) { //获取并更新节点
        auto it = key_to_node.find(key); //直接通过键值哈希查找
        if (it == key_to_node.end())  // 没有该节点
            return nullptr;
        auto node = it->second; // 有该节点
        remove(node); // 从当前链表抽出

        //找该节点的双链表
        push_front(++node->freq, node); // 放到下一层最前面
        return node;
    }

    // 创建一个新的双向链表头尾节点
    Node *new_list() {
        auto dummy = new Node(); // 哨兵节点
        dummy->prev = dummy;
        dummy->next = dummy;
        return dummy;
    }

    // 在对应频率的链表头添加一个节点
    void push_front(int freq, Node *x) {
        auto it = freq_to_dummy.find(freq);
        if (it == freq_to_dummy.end())  // 这个哈希还没有初始化双链表
            it = freq_to_dummy.emplace(freq, new_list()).first;

        //获取对应的双链表,并添加至头部(头插法)
        auto dummy = it->second;
        x->prev = dummy;
        x->next = dummy->next;
        x->prev->next = x;
        x->next->prev = x;
    }

    // 删除一个节点,删除在链表中的关系,同时链表为空后删除链表
    void remove(Node *x) {
        x->prev->next = x->next;
        x->next->prev = x->prev;

        //主要是判断更新当前最小频率双链表
        auto dummy = freq_to_dummy[x->freq];
        if (dummy->prev == dummy) { // 抽出来后,双链表为空
            freq_to_dummy.erase(x->freq); // 移除空链表
            delete dummy; // 释放内存
            if (min_freq == x->freq) //更新最小频率
                min_freq++;
        }
    }

    void release(){ //频率最小的双链表上,最后节点进行释放
        auto dummy = freq_to_dummy[min_freq];
        auto back_node = dummy->prev; // 找最后的节点
        key_to_node.erase(back_node->key); // 移除在哈希上的关系,这里relea直接删除,remove并不会直接删除
        remove(back_node); // 移除
        delete back_node;
    }

public:
    LFUCache(int capacity) : capacity(capacity) {}

    int get(int key) {
        auto node = get_node(key);
        return node ? node->value : -1;
    }

    void put(int key, int value) {
        auto node = get_node(key);
        if (node) { // 有这个节点
            node->value = value; // 更新 value
            return;
        }
        if (key_to_node.size() == capacity)  //释放内存
            release();
        
        key_to_node[key] = node = new Node(key, value); // 新节点
        push_front(1, node); // 放在「看过 1 次」的最上面
        min_freq = 1;
    }
};

标签:node,dummy,缓存,链表,LFU,key,freq,节点
From: https://www.cnblogs.com/929code/p/17727730.html

相关文章

  • 缓存击穿、缓存穿透、缓存雪崩等并发问题的解决思路
    在微服务应用中,每个细微的问题都可能由于并发被无限放大。在并发场景下,比较常见的有:秒杀活动中的商品超卖问题、数据冷热分离处理、缓存/数据库双写一致性问题、缓存击穿、缓存穿透、缓存雪崩问题等。在Java基础中,解决并发的思路就是锁,而锁的本质就是将并发执行串行化,在微服务应......
  • LRU缓存实现
    一.LRU缓存实现使用双向链表保证O(1)的优先度更改,同时当做优先队列维护插入顺序同时这里要结合哈希表,保证更改想要的节点/*定义Node双向链表节点定义remove进行删除节点(只删除节点在链表中的关系)定义update更新指定节点的优先度定义insert插入新的节点*/classLR......
  • 深入探讨Spring Boot中的Redis缓存
    介绍Redis是一种高性能的内存数据库,常用于缓存和消息队列等场景。在SpringBoot中,我们可以通过集成Redis来实现缓存功能。本文将深入探讨SpringBoot中的Redis缓存。集成Redis在SpringBoot中,我们可以通过添加以下依赖来集成Redis:<dependency><groupId>org.springframewor......
  • 力扣---146. LRU 缓存
    请你设计并实现一个满足  LRU(最近最少使用)缓存 约束的数据结构。实现 LRUCache 类:LRUCache(intcapacity) 以 正整数 作为容量 capacity 初始化LRU缓存intget(intkey) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。voidput(intkey,......
  • 记一次缓存一致性中延迟双删的使用场景
    1、背景: 前边写了个这样的业务需求:从算法服务那边会不断的发送过来一些预警的数据和预警恢复的数据,当有新预警数据过来时,会进行数据库记录和redis缓存,当有该预警的恢复过来时会将数据库状态修改并清除缓存,我的做法是使用了缓存双删的策略,即先删缓存,再更新数据库,再删缓存。但是......
  • UE4之DDC缓存
    什么是DDC(DerivedDataCache)?简单来说,是一些缓存文件。在使用Editor的过程中,有可能会在编辑某些文件,或者使用某些文件的时候产生额外的数据。为了避免每次都需要重新产生一次数据,所以第一次产生完数据之后,会将数据序列化,并以缓存的形式保存下来。DerivedDataCache目录包含了为引用......
  • springboot项目可以是那些缓存技术
    SpringBoot项目可以使用多种缓存技术,下面列举了一些常见的缓存技术以及它们的优缺点:Redis:优点:Redis是一个开源的内存数据结构存储,用作数据库、缓存和消息代理。其读写速度非常快,因为数据存储在内存中。Redis支持丰富的数据类型,如字符串、列表、集合、哈希、有序集合等,可以满足不同......
  • 三大缓存问题
    三大缓存问题缓存穿透什么是缓存穿透?怎么解决?缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。解决方案:最简单粗暴的方法如果一个查询返回的数据为空(不管是数......
  • Mybatis二级缓存
    Mybatis二级缓存还记得我们在学习Mybatis讲解的缓存机制吗,我们当时介绍了二级缓存,它是Mapper级别的缓存,能够作用与所有会话。但是当时我们提出了一个问题,由于Mybatis的默认二级缓存只能是单机的,如果存在多台服务器访问同一个数据库,实际上二级缓存只会在各自的服务器上生效,但是我......
  • ClickHouse数据缓存与性能优化技术实现最佳实践与案例
    前言ClickHouse是一款高性能的列式存储数据库,它的性能在处理海量数据时非常出色。但是,在实际应用中,我们还需要考虑如何进一步优化ClickHouse的性能,特别是在数据缓存方面。本文将深入探讨ClickHouse的数据缓存与性能优化技术实现最佳实践与案例。ClickHouse数据缓存ClickHouse的......