LRU Cache数据结构简介
什么是LRU Cache
LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。
Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用
会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
数据结构
LRU的典型实现是hash map + doubly linked list
, 双向链表用于存储数据结点,并且它是按照结点最近被使用的时间来存储的。 如果一个结点被访问了, 我们有理由相信它在接下来的一段时间被访问的概率要大于其它结点。于是, 我们把它放到双向链表的头部。当我们往双向链表里插入一个结点, 我们也有可能很快就会使用到它,同样把它插入到头部。 我们使用这种方式不断地调整着双向链表,链表尾部的结点自然也就是最近一段时间, 最久没有使用到的结点。那么,当我们的Cache满了, 需要替换掉的就是双向链表中最后的那个结点(不是尾结点,头尾结点不存储实际内容)。
如下是双向链表示意图,注意头尾结点不存储实际内容:
头 --> 结 --> 结 --> 结 --> 尾
结 点 点 点 结
点 <-- 1 <-- 2 <-- 3 <-- 点
假如上图Cache已满了,我们要替换的就是结点3。
哈希表的作用是什么呢?如果没有哈希表,我们要访问某个结点,就需要顺序地一个个找, 时间复杂度是O(n)。使用哈希表可以让我们在O(1)的时间找到想要访问的结点, 或者返回未找到。
Cache接口
当我们通过键值来访问类型为T的数据时,调用Get函数。如果键值为key的数据已经在 Cache中,那就返回该数据,同时将存储该数据的结点移到双向链表头部。 如果我们查询的数据不在Cache中,我们就可以通过Put接口将数据插入双向链表中。 如果此时的Cache还没满,那么我们将新结点插入到链表头部, 同时用哈希表保存结点的键值及结点地址对。如果Cache已经满了, 我们就将链表中的最后一个结点(注意不是尾结点)的内容替换为新内容, 然后移动到头部,更新哈希表。
C++代码
注意,hash map并不是C++标准的一部分,我使用的是Linux下g++ 4.6.1, hash_map放在/usr/include/c++/4.6/ext下,需要使用__gnu_cxx
名空间, Linux平台可以切换到c++的include目录:cd /usr/include/c++/版本 然后grep -iR “hash_map” ./ 查看在哪个文件中,一般头文件的最后几行会提示它所在的名空间。 当然如果你已经很fashion地在使用C++ 11,就不会有这些小困扰了。
#include <iostream>
#include <vector>
#include <hash_map> using namespace std;
using namespace stdext; template<class K, class T>
struct LRUCacheEntry
{
K key;
T data;
LRUCacheEntry* prev;
LRUCacheEntry* next;
}; template<class K, class T>
class LRUCache
{
private:
hash_map< K, LRUCacheEntry<K,T>* > _mapping;
vector< LRUCacheEntry<K,T>* > _freeEntries;// 存储可用结点的地址
LRUCacheEntry<K,T> * head;
LRUCacheEntry<K,T> * tail;
LRUCacheEntry<K,T> * entries;// 双向链表中的结点
public:
LRUCache(size_t size){
entries = new LRUCacheEntry<K,T>[size];
for (int i=0; i<size; i++)
_freeEntries.push_back(entries+i);// 存储可用结点的地址
head = new LRUCacheEntry<K,T>;
tail = new LRUCacheEntry<K,T>;
head->prev = NULL;
head->next = tail;
tail->next = NULL;
tail->prev = head;
}
~LRUCache()
{
delete head;
delete tail;
delete [] entries;
}
void put(K key, T data)
{
LRUCacheEntry<K,T>* node = _mapping[key];
if(node)
{
// refresh the link list
detach(node);
node->data = data;
attach(node);
}
else{
if ( _freeEntries.empty() )
{// 可用结点为空,即cache已满
node = tail->prev;
detach(node);
_mapping.erase(node->key);
node->data = data;
node->key = key;
attach(node);
}
else{
node = _freeEntries.back();
_freeEntries.pop_back();
node->key = key;
node->data = data;
_mapping[key] = node;
attach(node);
}
}
} T get(K key)
{
LRUCacheEntry<K,T>* node = _mapping[key];
if(node)
{
detach(node);
attach(node);
return node->data;
}
else return NULL;// 如果cache中没有,返回T的默认值。与hashmap行为一致
} private:
// 分离结点
void detach(LRUCacheEntry<K,T>* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
// 将结点插入头部
void attach(LRUCacheEntry<K,T>* node)
{
node->next = head->next;
node->prev = head;
head->next = node;
node->next->prev = node;
}
}; int main(){
hash_map<int, int> map;
map[9]= 999;
cout<<map[9]<<endl;
cout<<map[10]<<endl;
LRUCache<int, string> lru_cache(100);
lru_cache.Put(1, "one");
cout<<lru_cache.Get(1)<<endl;
if(lru_cache.Get(2) == "")
lru_cache.Put(2, "two");
cout<<lru_cache.Get(2);
return 0;
}
LRU Cache数据结构简介的更多相关文章
- 如何设计一个LRU Cache
如何设计一个LRU Cache? Google和百度的面试题都出现了设计一个Cache的题目,什么是Cache,如何设计简单的Cache,通过搜集资料,本文给出个总结. 通常的问题描述可以是这样: Q ...
- LeetCode:LRU Cache
题目大意:设计一个用于LRU cache算法的数据结构. 题目链接.关于LRU的基本知识可参考here 分析:为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::l ...
- LRU Cache
LRU Cache 题目链接:https://oj.leetcode.com/problems/lru-cache/ Design and implement a data structure for ...
- Java for LeetCode 146 LRU Cache 【HARD】
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- LeetCode之LRU Cache 最近最少使用算法 缓存设计
设计并实现最近最久未使用(Least Recently Used)缓存. 题目描述: Design and implement a data structure for Least Recently ...
- 【LRU Cache】cpp
题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...
- 146. LRU Cache
题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...
- LRU Cache的简单c++实现
什么是 LRU LRU Cache是一个Cache的置换算法,含义是“最近最少使用”,把满足“最近最少使用”的数据从Cache中剔除出去,并且保证Cache中第一个数据是最近刚刚访问的,因为这样的数据 ...
- [Swift]LeetCode146. LRU缓存机制 | LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
随机推荐
- typedef void (*funcptr)(void) typedef void (*PFV)(); typedef int32_t (*PFI)();
看到以下代码,不明白查了一下: /** Pointer to Function returning Void (any number of parameters) */ typedef void (* ...
- PAT甲级——A1111 Online Map【30】
Input our current position and a destination, an online map can recommend several paths. Now your jo ...
- <每日一题>题目3:编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件
def log(func): def inner(*args,**kwargs): with open('log',mode='a',encoding='utf-8') as f: #以追加的方式打开 ...
- CVE-2015-0235
看了下代码,由于各人能力问题,只是了解了漏洞的起因,知道哪里出了问题,但是不知道怎么用阿... 可怜... 这种漏洞,公布出来,只要不给EXP,估计威胁都不大.或者,就是我脑筋太死??? 首先,是网上 ...
- 12-5-上下文this
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- windows10 家庭版 无法远程2012的解决
windows 10安装最新补丁后无法远程windows server 2008.2012服务器 报错信息如下:出现身份验证错误,要求的函数不受支持 可能是由于CredSSP加密Oracle修正. ...
- ElasticSearch _bulk批量处理报错The bulk request must be terminated by a newline
在JSON数据最后回车换行,代码中可以
- android 头像选择以及裁剪
一.布局申明 <ImageView android:id="@+id/head_image" android:layout_width="80dp" an ...
- 当EntityFramework爱上AutoMapper
有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...
- ThinkCMF框架任意内容包含漏洞复现
1. 漏洞概述 ThinkCMF是一款基于PHP+MYSQL开发的中文内容管理框架,底层采用ThinkPHP3.2.3构建. 利用此漏洞无需任何权限情况下,构造恶意的url,可以向服务器写入任意内容的 ...