如何设计一个LRU Cache?

Google和百度的面试题都出现了设计一个Cache的题目,什么是Cache,如何设计简单的Cache,通过搜集资料,本文给出个总结。

通常的问题描述可以是这样:

Question:

[1] Design a layer in front of a system which cache the last n requests and the responses to them from the system.

在一个系统之上设计一个Cache,缓存最近的n个请求以及系统的响应。

what data structure would you use to implement the cache in the later to support following operations.

用什么样的数据结构设计这个Cache才能满足下面的操作呢?

[a] When a request comes look it up in the cache and if it hits then return the response from here and do not pass the request to the system

[b] If the request is not found in the cache then pass it on to the system

[c] Since cache can only store the last n requests, Insert the n+1th request in the cache and delete one of the older requests from the cache

因为Cache只缓存最新的n个请求,向Cache插入第n+1个请求时,从Cache中删除最旧的请求。

[d]Design one cache such that all operations can be done in O(1) – lookup, delete and insert.

Cache简介:

Cache(高速缓存), 一个在计算机中几乎随时接触的概念。CPU中Cache能极大提高存取数据和指令的时间,让整个存储器(Cache+内存)既有Cache的高速度,又能有内存的大容量;操作系统中的内存page中使用的Cache能使得频繁读取的内存磁盘文件较少的被置换出内存,从而提高访问速度;数据库中数据查询也用到Cache来提高效率;即便是Powerbuilder的DataWindow数据处理也用到了Cache的类似设计。Cache的算法设计常见的有FIFO(first in first out)和LRU(least
recently used)。根据题目的要求,显然是要设计一个LRU的Cache。

解题思路:

Cache中的存储空间往往是有限的,当Cache中的存储块被用完,而需要把新的数据Load进Cache的时候,我们就需要设计一种良好的算法来完成数据块的替换。LRU的思想是基于“最近用到的数据被重用的概率比较早用到的大的多”这个设计规则来实现的。

为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。每次数据项被查询到时,都将此数据项移动到链表头部(O(1)的时间复杂度)。这样,在进行过多次查找操作后,最近被使用过的内容就向链表的头移动,而没有被使用的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被使用的数据项,我们只需要将最新的数据项放在链表头部,当Cache满时,淘汰链表最后的位置就是了。



注: 对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关。其次,双向链表插入、删除很快,可以灵活的调整相互间的次序,时间复杂度为O(1)。



查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。

梳理一下思路:对于Cache的每个数据块,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev时双向链表的两个指针,key用于存储对象的键值,value用户存储要cache块对象本身。

Cache的接口:

查询:

  • 根据键值查询hashmap,若命中,则返回节点,否则返回null。
  • 从双向链表中删除命中的节点,将其重新插入到表头。
  • 所有操作的复杂度均为O(1)。

插入:

  • 将新的节点关联到Hashmap
  • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
  • 将新的节点插入到双向链表中头部

更新:

  • 和查询相似

删除:

  • 从双向链表和Hashmap中同时删除对应的记录。

LRU Cache的Java 实现:

public interface Cache<K extends Comparable, V> {

V get(K obj); //查询

void put(K key, V obj); //插入和更新

void put(K key, V obj, long validTime);

void remove(K key); //删除

Pair[] getAll();

int size();

}

public class Pair<K extends Comparable, V> implements Comparable<Pair> {

public Pair(K key1, V value1) {

this.key = key1;

this.value = value1;

}

public K key;

public V value;

public boolean equals(Object obj) {

if(obj instanceof Pair) {

Pair p = (Pair)obj;

return key.equals(p.key)&&value.equals(p.value);

}

return false;

}

@SuppressWarnings("unchecked")

public int compareTo(Pair p) {

int v = key.compareTo(p.key);

if(v==0) {

if(p.value instanceof Comparable) {

return ((Comparable)value).compareTo(p.value);

}

}

return v;

}

@Override

public int hashCode() {

return key.hashCode()^value.hashCode();

}

@Override

public String toString() {

return key+": "+value;

}

}

public class LRUCache<K extends Comparable, V> implements Cache<K, V>,

Serializable {

private static final long serialVersionUID = 3674312987828041877L;

Map<K, Item> m_map = Collections.synchronizedMap(new HashMap<K, Item>());

Item m_start = new Item(); //表头

Item m_end = new Item(); //表尾

int m_maxSize;

Object m_listLock = new Object(); //用于并发的锁

static class Item {

public Item(Comparable k, Object v, long e) {

key = k;

value = v;

expires = e;

}

public Item() {}

public Comparable key; //键值

public Object value; //对象

public long expires; //有效期

public Item previous;

public Item next;

}

void removeItem(Item item) {

synchronized(m_listLock) {

item.previous.next = item.next;

item.next.previous = item.previous;

}

}

void insertHead(Item item) {

synchronized(m_listLock) {

item.previous = m_start;

item.next = m_start.next;

m_start.next.previous = item;

m_start.next = item;

}

}

void moveToHead(Item item) {

synchronized(m_listLock) {

item.previous.next = item.next;

item.next.previous = item.previous;

item.previous = m_start;

item.next = m_start.next;

m_start.next.previous = item;

m_start.next = item;

}

}

public LRUCache(int maxObjects) {

m_maxSize = maxObjects;

m_start.next = m_end;

m_end.previous = m_start;

}

@SuppressWarnings("unchecked")

public Pair[] getAll() {

Pair p[] = new Pair[m_maxSize];

int count = 0;

synchronized(m_listLock) {

Item cur = m_start.next;

while(cur!=m_end) {

p[count] = new Pair(cur.key, cur.value);

++count;

cur = cur.next;

}

}

Pair np[] = new Pair[count];

System.arraycopy(p, 0, np, 0, count);

return np;

}

@SuppressWarnings("unchecked")

public V get(K key) {

Item cur = m_map.get(key);

if(cur==null) {

return null;

}

//过期则删除对象

if(System.currentTimeMillis()>cur.expires) {

m_map.remove(cur.key);

removeItem(cur);

return null;

}

if(cur!=m_start.next) {

moveToHead(cur);

}

return (V)cur.value;

}

public void put(K key, V obj) {

put(key, obj, -1);

}

public void put(K key, V value, long validTime) {

Item cur = m_map.get(key);

if(cur!=null) {

cur.value = value;

if(validTime>0) {

cur.expires = System.currentTimeMillis()+validTime;

}

else {

cur.expires = Long.MAX_VALUE;

}

moveToHead(cur); //成为最新的对象,移动到头部

return;

}

if(m_map.size()>=m_maxSize) {

cur = m_end.previous;

m_map.remove(cur.key);

removeItem(cur);

}

long expires=0;

if(validTime>0) {

expires = System.currentTimeMillis()+validTime;

}

else {

expires = Long.MAX_VALUE;

}

Item item = new Item(key, value, expires);

insertHead(item);

m_map.put(key, item);

}

public void remove(K key) {

Item cur = m_map.get(key);

if(cur==null) {

return;

}

m_map.remove(key);

removeItem(cur);

}

public int size() {

return m_map.size();

}

}

如何设计一个LRU Cache的更多相关文章

  1. 字节面试问我如何高效设计一个LRU,当场懵

    首发公众号:bigsai 转载请放置作者和原文(本文)链接 前言 大家好,我是bigsai,好久不见,甚是想念! 最近有个小伙伴跟我诉苦,说他没面到LRU,他说他很久前知道有被问过LRU的但是心想自己 ...

  2. 设计并实现一个LRU Cache

    一.什么是Cache 1 概念 Cache,即高速缓存,是介于CPU和内存之间的高速小容量存储器.在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器.其容量远小于内存,但速度却可以接近CP ...

  3. [转]如何用C++实现一个LRU Cache

    [转自http://hawstein.com/posts/lru-cache-impl.html] LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法 ...

  4. 用 Go 实现一个 LRU cache

    前言 早在几年前写过关于 LRU cache 的文章: https://crossoverjie.top/2018/04/07/algorithm/LRU-cache/ 当时是用 Java 实现的,最 ...

  5. 动手实现一个 LRU cache

    前言 LRU 是 Least Recently Used 的简写,字面意思则是最近最少使用. 通常用于缓存的淘汰策略实现,由于缓存的内存非常宝贵,所以需要根据某种规则来剔除数据保证内存不被撑满. 如常 ...

  6. LRU Cache

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

  7. 【Leetcode146】LRU Cache

    问题描述: 设计一个LRU Cache . LRU cache 有两个操作函数. 1.get(key). 返回cache 中的key对应的 val 值: 2.set(key, value). 用伪代码 ...

  8. LRU Cache leetcode java

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

  9. Redis(八) LRU Cache

    Redis(八)-- LRU Cache 在计算机中缓存可谓无所不在,无论还是应用还是操作系统中,为了性能都需要做缓存.然缓存必然与缓存算法息息相关,LRU就是其中之一.笔者在最先接触LRU是大学学习 ...

随机推荐

  1. Jenkin远程部署Tomcat8.5总结

    tomcat8.5相比之前的tomcat进入manger管理界面需要多一些设置 1. 在 $tomcathome/conf/Catalina/localhost/下创建 manager.xml , 填 ...

  2. python计算网络借贷和分期的年利率

    一.现金分期年利率 现在很多人都有使用网上借贷,动不动就消费分期.经过了解很多对贷款利率有一些误解,粗看觉得产生的利息也不是很高,但是年化利率到第是多少,这里面的玩法是怎样的呢. 拿某个借贷平台举例, ...

  3. 痢疾杆菌|SARS

    病原微生物分析: Eg:痢疾杆菌 测序---比对(K12vs治病菌1vs治病菌2),发现: “毒力岛”:K12没有,两个治病菌有,缩小搜寻范围. “黑洞”:K12有,但两个治病菌没有. SARS: 构 ...

  4. 一个简单WebApp的全程

    开始前,我先给出上一篇选项卡的demo链接http://xqhuadou.com/demo1/index.html.相信看着应该很带感,不过这个是之前经过修改的. 制作过程我就不多说了,可以直接看源码 ...

  5. 两个tomcat使用同一个jvm可能会出错

    如果两个tomcat中的项目的某些类具有完全相同的包路径和类名的话,jvm可能会“弄混”这两个类,所以一般要求包名“必须”唯一. 当然,如果两个类中的代码和import的类完全一样,弄混了也就弄混了, ...

  6. sqlserver 数据库分组后取第一条数据

    分享一个朋友的人工智能教程.零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助,点击查看教程. 比如查询用户某一天最后一笔交易后的账户余额 SELECT *( SELECT *, row_num ...

  7. Python笔记_第一篇_面向过程_第一部分_5.Python数据类型之列表类型(list)

    Python中序列是最基本的数据结构.序列中的每个元素都分配一个数字(他的位置或者索引),第一个索引是0,第二个索引是1,依次类推.Python的列表数据类型类似于C语言中的数组,但是不同之处在于列表 ...

  8. Codeforces Round #524 (Div. 2)C 二维坐标系求俩矩形面积交

    题:https://codeforces.com/contest/1080/problem/C 题意:给n*m的二维坐标系,每个位置(xi,yi)都表示一个方格,(1,1)的位置是白色,整个坐标系黑白 ...

  9. xcode6添加pch文件

    pch文件 定义:该文件中定义的内容为全局变量,可供所有类进行调用 例子:在pch文件中定义ios版本

  10. windows系统安装msi文件总提示2502、2503的错误

    首先: 1.按WIN+R,在运行框中输入“gpedit.msc” 确认:2.打开本地策略组编辑器后依次展开 :“计算机配置”->“管理模板”->“windows组件”->“windo ...