一、LRU缓存

LRU缓存

  • 1.LRU缓存介绍
  • 2.LRU缓存实现
  • 3.LRU缓存总结
    • 3.1 LRU 缓存的应用
    • 3.2 LRU 缓存的优缺点

1.LRU缓存介绍

LRU是Least Recently Used 的缩写,意为“最近最少使用”。它是一种常见的缓存淘汰策略,用于在缓存容量有限时,决定哪些数据需要被删除以腾出空间。
LRU 缓存的基本原则是:
①优先保留最近被访问的数据,因为这些数据在近期被再次访问的概率更高。
②淘汰最近最少使用的数据,因为它们被再次访问的可能性较小。

2.LRU缓存实现

接下来我将通过c语言中的glib库来实现一个LRU缓存结构。
首先给出结构体定义:

struct lruCache {GList *elem_queue;  // 使用 GList 作为双向链表存储缓存元素。int max_size;       // 缓存最大容量,<0 表示无限大小(INFI_Cache)。int size;           // 当前缓存已使用的大小。double hit_count;   // 命中次数统计。double miss_count;  // 未命中次数统计。void (*free_elem)(void *);                  // 用户定义的释放元素函数。int (*hit_elem)(void* elem, void* user_data); // 判断命中元素的回调函数。
};

需要实现如下功能:

struct lruCache* new_lru_cache(int size, void (*free_elem)(void *),int (*hit_elem)(void* elem, void* user_data));
// 创建一个新的 LRU 缓存,指定容量和自定义的释放与命中回调函数。void free_lru_cache(struct lruCache*);
// 释放缓存及其中的所有元素。void* lru_cache_lookup(struct lruCache*, void* user_data);
// 查找元素,若命中,将其移到链表头部;未命中返回 NULL。void* lru_cache_lookup_without_update(struct lruCache* c, void* user_data);
// 查找元素但不更新其在链表中的顺序。void* lru_cache_hits(struct lruCache* c, void* user_data,int (*hit)(void* elem, void* user_data));
// 模拟命中某个元素,满足自定义命中条件后将元素移到链表头部。void lru_cache_kicks(struct lruCache* c, void* user_data,int (*func)(void* elem, void* user_data));
// 删除满足用户自定义条件的元素。void lru_cache_insert(struct lruCache *c, void* data,void (*victim)(void*, void*), void* user_data);
// 插入新数据到缓存,若缓存已满则淘汰最久未使用的元素,并调用 victim 函数处理被淘汰的数据。int lru_cache_is_full(struct lruCache*);
// 检查缓存是否已满,已满返回 1,未满返回 0。

具体实现代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "lru_cache.h"struct lruCache* new_lru_cache(int size, void (*free_elem)(void *),int (*hit_elem)(void* elem, void* user_data)) {struct lruCache* c = (struct lruCache*) malloc(sizeof(struct lruCache));c->elem_queue = NULL;c->max_size = size;c->size = 0;c->hit_count = 0;c->miss_count = 0;c->free_elem = free_elem;c->hit_elem = hit_elem;return c;
}void free_lru_cache(struct lruCache *c) {if (c == NULL) return;  // 防止对 NULL 指针调用if (c->elem_queue != NULL) {// 确保对每个元素调用释放函数g_list_free_full(c->elem_queue, c->free_elem);c->elem_queue = NULL;  // 清空队列,防止重复释放}// 清理 lruCache 结构体本身free(c);
}/* find a item in cache matching the condition */
void* lru_cache_lookup(struct lruCache* c, void* user_data) {// 获取链表的第一个节点(链表头部)GList* elem = g_list_first(c->elem_queue);// 遍历链表,查找匹配的元素while (elem) {/** 使用回调函数 hit_elem 判断当前节点的数据是否与 user_data 匹配。* 回调函数由用户提供,自定义匹配逻辑。*/if (c->hit_elem(elem->data, user_data))break;  // 找到匹配的元素,退出循环// 继续遍历下一个节点elem = g_list_next(elem);}// 如果找到匹配的元素if (elem) {/** 将命中的元素移到链表头部,保持 LRU 缓存的访问顺序。* 1. 先从链表中移除该元素。* 2. 将该元素连接到链表头部。*/c->elem_queue = g_list_remove_link(c->elem_queue, elem);c->elem_queue = g_list_concat(elem, c->elem_queue);// 增加缓存命中计数c->hit_count++;// 返回命中元素的数据return elem->data;} else {// 如果未找到匹配的元素,增加缓存未命中计数c->miss_count++;return NULL;  // 返回 NULL 表示未命中}
}void* lru_cache_lookup_without_update(struct lruCache* c, void* user_data) {GList* elem = g_list_first(c->elem_queue);while (elem) {if (c->hit_elem(elem->data, user_data))break;elem = g_list_next(elem);}if (elem) {return elem->data;} else {return NULL;}
}/** Hit an existing elem for simulating an insertion of it.*/
void* lru_cache_hits(struct lruCache* c, void* user_data,int (*hit)(void* elem, void* user_data)) {GList* elem = g_list_first(c->elem_queue);while (elem) {if (hit(elem->data, user_data))break;elem = g_list_next(elem);}if (elem) {c->elem_queue = g_list_remove_link(c->elem_queue, elem);c->elem_queue = g_list_concat(elem, c->elem_queue);return elem->data;} else {return NULL;}
}/** We know that the data does not exist!*/
void lru_cache_insert(struct lruCache *c, void* data,void (*func)(void*, void*), void* user_data) {void *victim = NULL; // 存储被淘汰的数据// 检查缓存是否已满if (c->max_size > 0 && c->size == c->max_size) {// 获取链表尾部的节点(最久未使用的数据)GList *last = g_list_last(c->elem_queue);// 从链表中移除尾部节点c->elem_queue = g_list_remove_link(c->elem_queue, last);// 保存被淘汰的数据victim = last->data;// 释放链表节点(但不释放节点内的数据)g_list_free_1(last);// 更新缓存大小c->size--;}// 将新数据插入到链表头部(表示最近使用)c->elem_queue = g_list_prepend(c->elem_queue, data);// 更新缓存大小c->size++;// 如果有被淘汰的数据if (victim) {// 调用用户自定义回调函数处理被淘汰的数据(如果提供了 func)if (func)func(victim, user_data);// 调用 free_elem 回调释放被淘汰的数据c->free_elem(victim);}
}/** 从缓存中移除符合用户定义条件的元素。* * 参数:*   c          - 指向 lruCache 结构体的指针。*   user_data  - 用户自定义的数据,用于传递给回调函数 func。*   func       - 用户自定义的回调函数,用于判断当前元素是否需要被移除。*                返回非 0(true)表示移除该元素,返回 0(false)继续遍历。*/
void lru_cache_kicks(struct lruCache* c, void* user_data,int (*func)(void* elem, void* user_data)) {// 从链表尾部开始遍历(最久未使用的数据)GList* elem = g_list_last(c->elem_queue);// 遍历链表,向前移动,查找符合条件的节点while (elem) {/** 调用用户提供的回调函数 func,判断当前节点的数据是否符合移除条件。* 参数:*   elem->data  - 当前节点存储的数据。*   user_data   - 用户提供的上下文数据。*/if (func(elem->data, user_data)) break; // 如果找到符合条件的节点,退出循环elem = g_list_previous(elem); // 移动到前一个节点}// 如果找到了符合条件的节点if (elem) {/** 1. 从链表中移除该节点(但不释放节点内存和数据)。*    g_list_remove_link 返回移除后的链表。*/c->elem_queue = g_list_remove_link(c->elem_queue, elem);/** 2. 释放节点中存储的数据。*    调用用户提供的 free_elem 函数,确保数据被正确释放,防止内存泄漏。*/c->free_elem(elem->data);/** 3. 释放链表节点本身的内存。*    注意:g_list_free_1 只释放 GList 结构,不释放节点数据。*/g_list_free_1(elem);// 4. 更新缓存大小c->size--;}
}int lru_cache_is_full(struct lruCache* c) {return c->size >= c->max_size ? 1 : 0;
}

简单测试代码:

#include <stdio.h>
#include <stdlib.h>
#include "lru_cache.h"// 自定义释放函数:释放节点数据
void free_data(void* data) {printf("Freeing data: %d\n", *(int*)data);free(data);
}// 自定义匹配函数:判断数据是否匹配用户输入
int match_data(void* elem, void* user_data) {return (*(int*)elem == *(int*)user_data);
}// 主函数:测试 LRU 缓存
int main() {printf("---- Testing LRU Cache ----\n");// 创建一个容量为 3 的 LRU 缓存struct lruCache* cache = new_lru_cache(3, free_data, match_data);// 插入测试数据int* a = malloc(sizeof(int)); *a = 1;int* b = malloc(sizeof(int)); *b = 2;int* c = malloc(sizeof(int)); *c = 3;int* d = malloc(sizeof(int)); *d = 4;printf("Inserting: 1, 2, 3\n");lru_cache_insert(cache, a, NULL, NULL);lru_cache_insert(cache, b, NULL, NULL);lru_cache_insert(cache, c, NULL, NULL);// 查找数据:命中情况int target = 2;int* result = lru_cache_lookup(cache, &target);if (result) {printf("Cache hit: %d\n", *result);} else {printf("Cache miss: %d\n", target);}// 插入数据 4,导致数据 1 被淘汰printf("Inserting: 4 (This should evict 1)\n");lru_cache_insert(cache, d, NULL, NULL);// 再次查找数据 1:应该未命中target = 1;result = lru_cache_lookup(cache, &target);if (result) {printf("Cache hit: %d\n", *result);} else {printf("Cache miss: %d\n", target);}// 查找数据 3:应该命中target = 3;result = lru_cache_lookup(cache, &target);if (result) {printf("Cache hit: %d\n", *result);} else {printf("Cache miss: %d\n", target);}// 释放缓存free_lru_cache(cache);printf("---- LRU Cache Test Complete ----\n");return 0;
}

测试结果:
在这里插入图片描述
这是一个非常简单的测试代码,其实上面的LRU缓存实现是和数据类型无关的,因为我们通过函数指针提供了对数据的操作抽象,例如:
free_elem 回调:
允许用户自定义如何释放节点中的数据,可以适配不同类型的内存管理。
hit_elem 回调:
允许用户自定义数据匹配逻辑,适配任意类型的比较需求。
这些设计使得我们的缓存可以支持任意类型的数据,而不仅仅是局限在int类型。
下面我们来一个自定义数据类型的测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lru_cache.h"// 自定义数据类型:Person
typedef struct {char* name;int age;
} Person;// 自定义释放函数:释放 Person 类型的数据
void free_person(void* data) {Person* person = (Person*)data;printf("[Free] Name = %s, Age = %d\n", person->name, person->age);free(person->name); // 释放 name 字符串free(person);       // 释放 Person 结构体
}// 自定义匹配函数:根据姓名匹配 Person
int match_person(void* elem, void* user_data) {Person* person = (Person*)elem;char* target_name = (char*)user_data;return strcmp(person->name, target_name) == 0;
}// 工具函数:创建 Person 对象
Person* create_person(const char* name, int age) {Person* person = malloc(sizeof(Person));person->name = strdup(name); // 分配并复制 nameperson->age = age;return person;
}// 测试函数:打印缓存的命中率统计信息
void print_cache_stats(struct lruCache* cache) {printf("Cache Stats: Hits = %.0f, Misses = %.0f, Hit Rate = %.2f%%\n",cache->hit_count,cache->miss_count,cache->hit_count / (cache->hit_count + cache->miss_count) * 100.0);
}int main() {printf("---- Comprehensive LRU Cache Test ----\n");// 创建容量为 3 的 LRU 缓存struct lruCache* cache = new_lru_cache(3, free_person, match_person);// 插入数据:Person 结构体printf("Inserting: Alice, Bob, Charlie\n");lru_cache_insert(cache, create_person("Alice", 25), NULL, NULL);lru_cache_insert(cache, create_person("Bob", 30), NULL, NULL);lru_cache_insert(cache, create_person("Charlie", 35), NULL, NULL);// 查找数据:命中 Bobprintf("Looking up: Bob\n");char* target_name = "Bob";Person* result = (Person*)lru_cache_lookup(cache, target_name);if (result) {printf("[Hit] Found: Name = %s, Age = %d\n", result->name, result->age);} else {printf("[Miss] Not found: %s\n", target_name);}// 插入新数据 Dave,触发淘汰最久未使用的数据 Aliceprintf("Inserting: Dave (Evicts Alice)\n");lru_cache_insert(cache, create_person("Dave", 40), NULL, NULL);// 查找 Alice:未命中printf("Looking up: Alice\n");target_name = "Alice";result = (Person*)lru_cache_lookup(cache, target_name);if (result) {printf("[Hit] Found: Name = %s, Age = %d\n", result->name, result->age);} else {printf("[Miss] Not found: %s\n", target_name);}// 查找 Charlie:命中printf("Looking up: Charlie\n");target_name = "Charlie";result = (Person*)lru_cache_lookup(cache, target_name);if (result) {printf("[Hit] Found: Name = %s, Age = %d\n", result->name, result->age);} else {printf("[Miss] Not found: %s\n", target_name);}// 打印缓存的命中率统计信息print_cache_stats(cache);// 释放缓存printf("Freeing the cache...\n");free_lru_cache(cache);printf("---- LRU Cache Test Complete ----\n");return 0;
}

测试结果:
在这里插入图片描述
上面的LRU缓存代码其实是一个设计精巧、功能全面的缓存实现,具有高度的通用性和灵活性。通过将缓存管理逻辑与数据操作解耦,提供了标准化的接口,包括 元素插入、查找、淘汰、命中统计等功能,同时通过回调函数支持任意数据类型的自定义释放和匹配逻辑。
核心优势总结:
①模块化设计:通过free_elem和hit_elem回调函数,适配不同数据类型,用户无需修改核心代码即可实现各种缓存需求。
②功能丰富:支持 LRU 淘汰策略,自动移除最久未使用的数据。提供查找、插入、条件删除、命中模拟等多种操作接口。统计命中次数和未命中次数,便于分析缓存性能。
③内存管理安全:使用 free_elem 回调释放数据,确保内存不会泄漏。
④易于扩展:代码逻辑清晰,接口简单易用,方便进一步添加功能,如并发支持、过期数据清理等

3.LRU缓存总结

接下来我将通过两个表格来简要描述一下LRU缓存的应用以及优缺点。

3.1 LRU 缓存的应用

应用场景描述作用
操作系统内存管理用于页面置换机制,替换最久未使用的内存页。提高内存利用率,减少磁盘 I/O。
数据库缓存缓存频繁访问的数据,淘汰不常用的数据。提高查询性能,减少查询延迟。
Web 浏览器缓存缓存网页资源(如 HTML、图片、CSS 等),加快访问速度。减少重复下载,提升用户体验。
CDN 内容分发网络缓存热点内容,替换最少访问的资源。减少带宽消耗,加速内容传输。
嵌入式系统管理资源受限设备中的数据缓存。提高执行效率,优化内存占用。
数据流处理与缓存临时缓存大数据处理中间结果,腾出空间以继续处理新的数据。提升处理速度,避免重复计算。

3.2 LRU 缓存的优缺点

类别描述
优点1. 实现简单:逻辑清晰,易于实现。
2. 适应时间局部性:能很好处理最近访问的数据,提高缓存命中率。
3. 广泛适用:适用于多种缓存管理场景,如内存、数据库、浏览器等。
缺点1. 空间开销大:需额外使用链表和哈希表维护数据顺序,增加内存消耗。
2. 性能瓶颈:若未优化,查找和移动数据时间复杂度较高(O(N))。
3. 非最佳策略:在数据访问均匀分布或随机的情况下,命中率较低,效果不佳。
4. 线程安全问题:在多线程环境下,需要额外加锁保护,影响性能。

总结:LRU 缓存在操作系统、数据库、Web 浏览器等场景中具有广泛应用,优点是实现简单、适应时间局部性,但也存在空间开销和性能瓶颈等缺点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/63086.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW光栅衍射虚拟仿真系统

随着现代教育技术的快速发展&#xff0c;虚拟仿真实验平台逐渐成为物理实验教学的重要辅助工具。基于LabVIEW的平面透射光栅虚拟仿真系统帮助学生更好地理解和分析光栅衍射现象&#xff0c;提高教学质量和学生的学习兴趣。 项目背景 在波动光学的教学中&#xff0c;光栅衍射实…

Swin Transformer:用Transformer实现CNN多尺度操作

文本是关于Swin Transformer基础知识的了解 论文&#xff1a;https://arxiv.org/pdf/2103.14030 项目&#xff1a;https://github. com/microsoft/Swin-Transformer. 实现一个Swin Transformer&#xff1a;Swin Transformer模型具体代码实现-CSDN博客 Swin Transformer mlp…

系列2:基于Centos-8.6Kubernetes 集成GPU资源信息

每日禅语 自省&#xff0c;就是自我反省、自我检查&#xff0c;自知己短&#xff0c;从而弥补短处、纠正过失。佛陀强调自觉觉他&#xff0c;强调以达到觉行圆满为修行的最高境界。要改正错误&#xff0c;除了虚心接受他人意见之外&#xff0c;还要不忘时时观照己身。自省自悟之…

flutter控件buildDragTargetWidget详解

文章目录 1. DragTarget 的核心概念基本属性 2. 基本用法3. 使用 buildDragTargetWidget4. 常见场景5. 注意事项 buildDragTargetWidget 不是 Flutter 中的内置 API 或方法&#xff0c;但根据命名习惯&#xff0c;它很可能是您正在实现或使用的一个方法&#xff0c;用于在 Flut…

【数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 测试说明 我的通关代码: 测试结果&#xff1a; 任务描述 本关任务&#xff1a;实现二路归并算法。 测试说明 平台会对你编写的代码进行测试&#xff1a; 测试输入示例&#xff1a; 11 18 2 20 34 12 32 6 16 5 8 1 (说明&#xff1a;第一行是元…

【FFmpeg】FFmpeg 内存结构 ⑥ ( 搭建开发环境 | AVPacket 创建与释放代码分析 | AVPacket 内存使用注意事项 )

文章目录 一、搭建开发环境1、开发环境搭建参考2、项目搭建 二、AVPacket 创建与释放代码分析1、AVPacket 创建与释放代码2、Qt 单步调试方法3、单步调试 - 分析 AVPacket 创建与销毁代码 三、AVPacket 内存使用注意事项1、谨慎使用 av_init_packet 函数2、av_init_packet 函数…

2024首届世界酒中国菜国际地理标志产品美食文化节成功举办篇章

2024首届世界酒中国菜国际地理标志产品美食文化节成功举办&#xff0c;开启美食文化交流新篇章 近日&#xff0c;首届世界酒中国菜国际地理标志产品美食文化节在中国国际地理标志大厦成功举办&#xff0c;这场为期三天的美食文化盛会吸引了来自世界各地的美食爱好者、行业专家…

AI发展与LabVIEW程序员就业

人工智能&#xff08;AI&#xff09;技术的快速发展确实对许多行业带来了变革&#xff0c;包括自动化、数据分析、软件开发等领域。对于LabVIEW程序员来说&#xff0c;AI的崛起确实引发了一个值得关注的问题&#xff1a;AI会不会取代他们的工作&#xff0c;导致大量失业&#x…

展柜设计公司平面布置小程序的分析与设计springboot+论文源码调试讲解

3系统的需求分析 需求分析的任务是通过详细调查展柜设计公司平面布置小程序软件所需的对象&#xff0c;充分了解系统的工作概况&#xff0c;明确功能实现的各种需求&#xff0c;然后在此基础上确定系统的功能。系统必须充分考虑今后可能的扩充和改变。 3.1可行性分析 通过对…

家校通小程序实战教程10部门管理前后端连接

目录 1 加载后端的数据2 为什么不直接给变量赋值3 保存部门信息4 最终的效果5 总结 现在部门管理已经完成了后端功能和前端开发&#xff0c;就需要在前端调用后端的数据完成界面的展示&#xff0c;而且在录入部门信息后需要提交到数据库里&#xff0c;本篇我们介绍一下前后端如…

Java并发编程学习(二)

线程的状态 有说5种的&#xff0c;有说6种的 5种的&#xff0c;从操作系统层面来讲 初始状态&#xff1a;也就是语言层面创建了线程对象&#xff0c;还未与操作系统线程关联。Java中也就是new了一个线程&#xff0c;还未调用。可运行状态&#xff1a;&#xff08;就绪状态&a…

Docker方式安装人人影视离线完整安装包

本文软件由网友 ルリデ 推荐&#xff1b; 上周&#xff0c;人人影视创始人宣布将人人影视二十年字幕数据开源分享 目前提供了两种使用方式&#xff1a; “在线应用” &#xff1a;意味着需要有互联网才可以使用。官方提供了网站&#xff1a;https://yyets.click “离线使用” …

SpringBoot 学习

SpringBoot 学习 什么是 Springboot Spring Boot 是 Spring 提供的一个子项目&#xff0c;用于快速构建 Spring 应用程序 传统的问题&#xff1a; 导入依赖繁琐项目配置繁琐 SpringBoot 的特性 起步依赖&#xff1a;整合所有 web 的依赖配置好了自动配置&#xff1a;bean…

最新全开源IM即时通讯系统源码(PC+WEB+IOS+Android)部署指南

全开源IM&#xff08;即时通讯&#xff09;系统源码部署是一个复杂但系统的过程&#xff0c;涉及多个组件和步骤。以下是一个详细的部署指南&#xff0c;旨在帮助开发者或系统管理员成功部署一个全开源的IM系统&#xff0c;如OpenIM。      IM即时通讯系统源码准备工作   …

CAD c# 生成略缩图预览

代码如下&#xff1a; using (Transaction tr currentdb.TransactionManager.StartTransaction()){//当前数据库开启事务using (Database tempdb new Database(false, true)) //创建临时数据库(两个参数&#xff1a;是否创建符号表&#xff0c;不与当前文档关联){try{Bitmap …

[面试题]--索引用了什么数据结构?有什么特点?

答&#xff1a;使用了B树&#xff1a; 时间复杂度&#xff1a;O(logN),可以有效控制树高 B树特点&#xff1a; 1.叶子节点之间有相互链接的作用&#xff0c;会指向下一个相近的兄弟节点。 MySQL在组织叶子节点使用的是双向链表 2.非叶子节点的值都保存在叶子节点当中 MySQL非叶…

ansible自动化运维(五)roles角色管理

Roles角色管理 角色&#xff08;roles&#xff09;是ansible自1.2版本开始引入的新特性&#xff0c;用于层次性&#xff0c;结构化地组织playbook。 roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单的…

操作系统:文件系统

目录 1、文件 概念&#xff1a; UNIX文件分类&#xff1a; 2、文件系统 3、文件的访问方式 顺序访问 随机访问 4、文件的组织 逻辑组织 物理组织 5、倒排结构&#xff08;了解&#xff09; 5、文件目录 文件控制块&#xff08;FCB&#xff09; ​编辑 目录项 单…

单元测试-FATAL ERROR in native method: processing of -javaagent failed

文章目录 前言单元测试-FATAL ERROR in native method: processing of -javaagent failed1. 报错信息2. 解决方案 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运…

决策曲线分析(DCA)中平均净收益用于评价模型算法(R自定义函数)

决策曲线分析&#xff08;DCA&#xff09;中平均净收益用于评价模型算法 DCA分析虽然不强调用来评价模型算法或者变量组合的优劣&#xff0c;但是实际应用过程中感觉DCA曲线的走势和模型的效能具有良好的一致性&#xff0c;其实这种一致性也可以找到内在的联系&#xff0c;比如…