本文阅读目录
1、缓存是什么
2、什么是LRU缓存
3、LRU缓存设计思路
4、LRU缓存接口
缓存是什么
其实缓存的思想在生活中处处可见,比如说我们的书架上摆满了各种书籍,但是我最近想集中精力攻克数据结构这门课,因次为了方便,我将与数据结构有关的书先从书架取下来,放在我们的书桌上,这样我们取书的时候就快很多了,伸手可得,而不用跑到书架旁取书了,在这个例子里,书桌所扮演的就是缓存的角色。
在计算机中,我们知道,访问磁盘的效率比访问内存的效率低得多,低多少了,大概是10W级别吧,如下图所示。 我们知道有个很著名的二八定律。其实计算机世界中也有很多情况遵守这个定律,比如说网站访问的特点:80% 的业务访问集中在20% 的数据上。那么很自然就想到:既然大部分业务访问集中在一小部分数据上,那么如果通过设计合理的数据结构把这一小部分数据存放在内存中,是不是极大的提高了系统的性能呢,这就是缓存。
从上面可以看出,无论是生活中还是计算机世界中的缓存,主要有以下两个特点:
-
提高系统效率,
-
容量小于外部存储容量,如书桌容量小于书架容量,内存容量小于硬盘容量
什么是LRU缓存
由于内存是有限的,因此缓存总有满的时候,那么当缓存满了的时候,这时候又有新的数据需要加入到缓存中时,我们该怎么办了?没什么别的办法,只有从缓存中删除旧数据为新数据腾出空间,那么究竟删除原来缓存的哪些数据了?这就涉及到缓存的替换策略,LRU就是一种缓存策略。
LRU,Least Recently Used的简写,翻译过来就是“最近最少使用”。该算法依据于程序的局部性原理, 其淘汰旧数据的策略是,距离当前最久没有被访问过的数据应该被淘汰。
举个例子:现在有一个能存放5个数据的缓存,刚开始中缓存中存放了3个数据A->B->C,如下所示: 接着又在缓存中插入2个数据D->E,此时缓存已经满了。如下: 好了,现在我们来访问数据A,现在A成为了最近访问的数据,B成了最久没有访问过的数据: 现在我们又有一个数据F想要插入到缓存中,由于缓存已经满了,需要从缓存中淘汰旧数据,此时缓存中的数据B是距离当前时间最久没有被访问过的数据,根据LRU算法,我们会淘汰掉数据B:将F插入到当前B的位置。 此时数据C成为了距离当前时间最久没有被访问过的数据,因此如果再来一个数据的话,就应该插入到C当前的位置,依次类推。
LRU缓存设计
理解LRU缓存的思想其实挺简单,但是要自己动手设计一个LRU缓存就并不那么简单了,这节主要讲讲LRU缓存的设计思路。
首先从前面的分析可以看出,LRU缓存主要的操作是插入删除以及查找,因此我们可以考虑用双链表来维护缓存,该链表将缓存数据按访问时间从新到旧排列起来。
如果我们只是单存将缓存中的数据维护在一个双向链表中, 那么当我们需要从缓存中查找数据时,需要遍历链表,其时间复杂度为O(n)。 这样的设计, 是一种比较低效率的做法。 因此, 我们除了将数据维护在双向链表中, 我们同时还将数据维护在一个哈希表中。 哈希表访问数据的时间复杂度为O(1)。
依据上述设计, 一个LRU缓存包含了一个双向链表和一个哈希表, 双向链表以及哈希表的一个节点代表缓存中的一个缓存单元, 因此我们可以这样定义我们缓存单元的数据结构:
//LRU缓存的缓存单元
typedef struct cacheEntryS
{
char key; //数据的key
char data; // 数据的data
struct cacheEntryS *hashListPrev; //指向哈希链表的前一个元素
struct cacheEntryS *hashListNext; //指向哈希链表的后一个元素
struct cacheEntryS *lruListPrev; //指向链表的前一个元素
struct cacheEntryS *lruListNext; //指向链表后一个元素
}cacheEntryS;
LRU缓存的数据结构如下:
//定义LRU缓存
typedef struct LRUCacheS
{
int cacheCapacity; //缓存的容量
cacheEntryS **hashMap; //缓存的哈希表
cacheEntryS *lruListHead;//缓存的双向链表表头
cacheEntryS *lruListTail;//缓存的双向链表表尾
int lruListSize; //缓存的双向链表节点个数
}LRUCacheS;
如果一大坨代码看的比较晕,那么对着下面的结构关系图应该能理解的比较透彻,注意下图中没有指向的指针都是NULL:
LRU缓存接口设计
通过前面的分析不难看出,对一个LRU缓存的最重要的操作无外乎是创建、销毁、插入数据、查找数据。下面给出这几个接口,至于具体实现,这里先不分析。后面有机会再一一分析。
/*********************************************
函数名:LRUCacheCreate
功能:创建LRU缓存
输入参数:capacity,缓存的数据容量
输出参数:lruCache,指向新建缓存的指针
针返回值:0---成功 -1---失败
*********************************************/
int LRUCacheCreate(int capacity, void **lruCache);
/*********************************************
函数名:LRUCacheDestory
功能:销毁LRU缓存
输入参数:lruCache指向新建缓存的指针
输出参数:无
针返回值:0---成功 -1---失败
*********************************************/
int LRUCacheDestory(void *lruCache);
/*********************************************
函数名:LRUCacheSet
功能:将数据插入到LRU缓存中
输入参数:key:数据索引 data:数据内容
输出参数:无
针返回值:0---成功 -1---失败
*********************************************/
int LRUCacheSet(void *lruCache, char key, char data);
/*********************************************
函数名:LRUCacheGet
功能:将数据插入到LRU缓存中
输入参数:key:数据索引
输出参数:无
针返回值:缓存中存在key对应的data,返回
缓存中不存在key对应的data,返回'\0'
*********************************************/
int LRUCacheGet(void *lruCache, char key);
推荐阅读:
【福利】自己搜集的网上精品课程视频分享(上) 【协议森林】邮差与邮局 (网络协议概观)
【数据结构与算法】 通俗易懂讲解 位排序 【C++札记】C++11并发编程(一)开启线程之旅 【C++札记】C/C++指针使用常见的坑 【C++札记】静态库和动态库详解(上)
码农有道 coding
码农有道,为您提供通俗易懂的技术文章,让技术变的更简单!