什么是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数据结构简介的更多相关文章

  1. 如何设计一个LRU Cache

    如何设计一个LRU Cache? Google和百度的面试题都出现了设计一个Cache的题目,什么是Cache,如何设计简单的Cache,通过搜集资料,本文给出个总结. 通常的问题描述可以是这样: Q ...

  2. LeetCode:LRU Cache

    题目大意:设计一个用于LRU cache算法的数据结构. 题目链接.关于LRU的基本知识可参考here 分析:为了保持cache的性能,使查找,插入,删除都有较高的性能,我们使用双向链表(std::l ...

  3. LRU Cache

    LRU Cache 题目链接:https://oj.leetcode.com/problems/lru-cache/ Design and implement a data structure for ...

  4. Java for LeetCode 146 LRU Cache 【HARD】

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  5. LeetCode之LRU Cache 最近最少使用算法 缓存设计

    设计并实现最近最久未使用(Least Recently Used)缓存. 题目描述: Design and implement a data structure for Least Recently ...

  6. 【LRU Cache】cpp

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...

  7. 146. LRU Cache

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...

  8. LRU Cache的简单c++实现

    什么是 LRU LRU Cache是一个Cache的置换算法,含义是“最近最少使用”,把满足“最近最少使用”的数据从Cache中剔除出去,并且保证Cache中第一个数据是最近刚刚访问的,因为这样的数据 ...

  9. [Swift]LeetCode146. LRU缓存机制 | LRU Cache

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

随机推荐

  1. iOS开发之SceneKit框架--SCNLight.h

    1.SCNLight简介 用于添加光源,连接到一个节点照亮现场,可以给场景添加不同的灯光,模拟逼真的环境. 2.四种灯光的简介 添加一个box立方体.一个tube圆柱管道和一个地板floor,没有灯光 ...

  2. git撤销修改及版本回退

    场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file. 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步, ...

  3. Spring - 框架入门

    认识 Spring 框架 Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP( ...

  4. 02_mybatis开发dao的方法

    MyBatis开发dao的方法 1. SqlSession使用范围 1.1 SqlSessionFactoryBuilder ​ 通过SqlSessionFactoryBuilder创建会话工厂Sql ...

  5. 【转载】flex布局超过显示省略号

    <div class="main"> <div class="content"> <h4 class="name&quo ...

  6. READING | 我是一只IT小小鸟

    “世界是如此的熙熙攘攘,让年轻的心找不到方向,但这些人是不能小看的啊,如果他们开始敲打自己的命令行.” “知之者不如好知者,好之者不如乐之者”,很多IT界的优秀人才都对计算机技术或者IT技术有着浓厚的 ...

  7. T2980 LR棋盘【Dp+空间/时间优化】

    Online Judge:未知 Label:Dp+滚动+前缀和优化 题目描述 有一个长度为1*n的棋盘,有一些棋子在上面,标记为L和R. 每次操作可以把标记为L的棋子,向左移动一格,把标记为R的棋子, ...

  8. Android开发 控件阴影详情

    如何给控件设置阴影? <com.google.android.material.tabs.TabLayout android:id="@+id/tablayout" andr ...

  9. 解决 no compatible version found: ionic-native@^3.5.0

    npm ERR! Linux --generic npm ERR! argv "/usr/src/node-v6.10.3-linux-x64/bin/node" "/u ...

  10. Georgia and Bob

    Georgia and Bob 给出一个严格递增的正整数数列\(\{a_i\}\),每一次操作可以对于其中任意一个数减去一个正整数,但仍然要保证数列的严格递增性,现在两名玩家轮流操作,不能操作的玩家判 ...