设计并实现最近最久未使用(Least Recently Used)缓存。

题目描述:

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

设计并实现最近最久未使用的缓存数据结构,支持 get 和 set 操作.

get()-如果 key 存在,返回对应的 value 值,否则返回 -1.

set()-插入 key 对应的 value 到缓存中,如果缓存已满,将最近最久未使用的元素从缓存中移除。

要实现这个设计,我们先回顾一下大学课堂上的知识。
LRU,即最近最少使用,是操作系统内存管理的一种页面置换算法,
常见的页面置换算法,最佳置换算法(OPT,理想置换算法),先进先出置换算法(FIFO),
最近最久未使用算法(LRU),最少使用算法。

其中,最佳置换算法是一种理想情况下的页面置换算法,实际上不可能实现。该算法的基本思想是发生缺页时,有些页面在内存中,其中有一页将很快被访问(也包含紧接着的下一条指令的那页),而其他页面则可能要到10、100或者1000条指令后才会被访问,每个页面都可以用在该页面首次被访问前所要执行的指令数进行标记。最佳页面置换算法规定标记最大的页应该被置换。但当缺页发生时,操作系统无法知道各个页面下一次是在什么时候被访问。这个算法无法实现,但可以用于对可实现算法的性能进行衡量。

另外两种主要算法,LFU算法-实现缓存,FIFO算法-实现缓存,可以查看这里

LRU的实现方法有很多,传统的LRU实现方法:

1.计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。
2.栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。

(1)使用 LinkedHashMap实现Lrucache

Java语言可以利用 LinkedHashMap, LinkedHashMap 是有序的哈希表,可以保存记录的插入顺序,并且按使用顺序排列。
重写其中的removeEldestEntry(Map.Entry)方法,就可以实现LRU算法。

在Mysql Jdbc Util和Apache的很多Jar包中,都是使用LinkedHashMap实现LRUCache。
下面的代码来自mysql-connector-java-5.1.18-bin.jar

package com.mysql.jdbc.util;

import java.util.LinkedHashMap;
import java.util.Map; public class LRUCache extends LinkedHashMap
{ public LRUCache(int maxSize)
{
super(maxSize, 0.75F, true);
maxElements = maxSize;
} protected boolean removeEldestEntry(java.util.Map.Entry eldest)
{
return size() > maxElements;
} private static final long serialVersionUID = 1L;
protected int maxElements;
}

  

不过LeetCode的OJ肯定不支持这样实现,上面的代码修改后提交,提示 Comoile Error 。

(2)使用双向链表实现

JDK中,LinkedHashMap是通过继承HashMap,维护一个双向链表实现,

当某个Cache位置被命中,通过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,在多次进行Cache操作后,最近使用的Cache就会向链表头部移动,链表尾部就是命中次数最少,最久未使用的Cache。
空间充满时,移除尾部的数据就可以了。有几点需要注意,一个是Key不存在的情况,一个是缓存设计要求Key唯一。

下面使用双向链表实现LRU Cache,主要是维护一个缓存设定容量,当前容量,以及双向链表的头尾节点,方便移动和删除。

import java.util.HashMap;
/**
* 近期最少使用算法 设计缓存
*/
public class LRUCache { private int cacheSize;//缓存容量
private int currentSize;//当前容量
private HashMap<Object, CacheNode> nodes;//缓存容器
private CacheNode head;//链表头
private CacheNode last;//链表尾 class CacheNode{
CacheNode prev;//前一节点
CacheNode next;//后一节点
int value;//值
int key;//键
CacheNode() {
}
} //初始化缓存
public LRUCache(int capacity) {
currentSize=0;
cacheSize=capacity;
nodes=new HashMap<Object, CacheNode>(capacity);
} public Integer get(int key) {
CacheNode node = nodes.get(key);
if (node != null) {
move(node);
return node.value;
} else {
return -1;//error code
} } public void set(int key, int value) {
CacheNode node = nodes.get(key);
//重复Key
if(node!=null){
node.value=value;
move(node);
nodes.put(key, node);
}else
{//key未重复,正常流程
node =new CacheNode();
if(currentSize>=cacheSize){
if (last != null){//缓存已满,进行淘汰
nodes.remove(last.key);}
removeLast();//移除链表尾部并后移
}else{
currentSize++;
} node.key=key;
node.value=value;
move(node);
nodes.put(key, node);
}
} //移动链表节点至头部
private void move(CacheNode cacheNode){
if(cacheNode==head)
return;
//链接前后节点
if(cacheNode.prev!=null)
cacheNode.prev.next=cacheNode.next;
if(cacheNode.next!=null)
cacheNode.next.prev=cacheNode.prev;
//头尾节点
if (last == cacheNode)
last = cacheNode.prev;
if (head != null) {
cacheNode.next = head;
head.prev = cacheNode;
}
//移动后的链表
head = cacheNode;
cacheNode.prev = null;
//节点唯一的情况
if (last == null)
last = head;
} //移除指定缓存
public void remove(int key){
CacheNode cacheNode = nodes.get(key);
if (cacheNode != null) {
if (cacheNode.prev != null) {
cacheNode.prev.next = cacheNode.next;
}
if (cacheNode.next != null) {
cacheNode.next.prev = cacheNode.prev;
}
if (last == cacheNode)
last = cacheNode.prev;
if (head == cacheNode)
head = cacheNode.next;
} }
//删除尾部的结点,即去除最近最久未使用数据
private void removeLast(){
if(last!=null){
if(last.prev!=null){
last.prev.next=null;
}else{//空间大小为1的情况
head = null;
}
last = last.prev;
}
} public void clear() {
head = null;
last = null;
}
//测试用例
// public static void main(String[] args){
// LRUCache lCache=new LRUCache(2);
// lCache.set(2, 1);
// lCache.set(1, 1);
// lCache.set(2, 3);
// lCache.set(4, 1);
// System.out.println(lCache.get(1));
// System.out.println(lCache.get(2));
//
// } }

LeetCode之LRU Cache 最近最少使用算法 缓存设计的更多相关文章

  1. [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器

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

  2. [LeetCode] 146. LRU Cache 近期最少使用缓存

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

  3. 146 LRU Cache 最近最少使用页面置换算法

    设计和实现一个  LRU(最近最少使用)缓存 数据结构,使它应该支持以下操作: get 和 put .get(key) - 如果密钥存在于缓存中,则获取密钥的值(总是正数),否则返回 -1.put(k ...

  4. [LeetCode] LRU Cache 最近最少使用页面置换缓存器

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

  5. LeetCode OJ:LRU Cache(最近使用缓存)

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

  6. Java for LeetCode 146 LRU Cache 【HARD】

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

  7. 【LeetCode】LRU Cache 解决报告

    插话:只写了几个连续的博客,博客排名不再是实际"远在千里之外"该.我们已经进入2一万内. 再接再厉.油! Design and implement a data structure ...

  8. leetcode 146. LRU Cache 、460. LFU Cache

    LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面,如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目. LFU ...

  9. leetcode 146. LRU Cache ----- java

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

随机推荐

  1. Get与Post比较

  2. Initialization of deep networks

    Initialization of deep networks 24 Feb 2015Gustav Larsson As we all know, the solution to a non-conv ...

  3. hdu 2050 折线分割平面

    训练递推用题,第一次做这个题,蒙的,而且对了. #include <stdio.h> int main(void) { int c,a; scanf("%d",& ...

  4. Graphtree--zabbix增强功能(一屏展示所有内容)

    Graphtree--zabbix增强功能 Graphtree由OneOaaS开发并开源出来. 功能 集中展示所有分组设备 集中展示一个分组图像 集中展示一个设备图像 展示设备下的Applicatio ...

  5. Windows 下的.NET+ Memcached安装

    转载请标明出处: http://www.yaosansi.com/ 原文:http://www.yaosansi.com/post/1396.html Memcached官方:http://danga ...

  6. linux 访问tomcat 管理页面时 You are not authorized to view this page 403(真实可用)

    ava代码 收藏代码 You are not authorized to view this page. If you have not changed any configuration files ...

  7. log4j:ERROR setFile(null,true) call failed.java.io.FileNotFoundException: ..\logs\2010-1-19.log (系统找不到指定的路径。)

    log4j:ERROR setFile(null,true) call failed.java.io.FileNotFoundException: ..\logs\2010-1-19.log (系统找 ...

  8. C++标准库异常类

    C++标准库异常类 2012-12-24 16:27 5269人阅读 评论(1) 收藏 举报  分类: c/c++(36)  C++标准库异常类继承层次中的根类为exception,其定义在excep ...

  9. 繁华模拟赛 Vicent坐电梯

    /*n<=5000­这样就不能用O(n)的转移了,而是要用O(1)的转移.­注意我们每次的转移都来自一个连续的区间,而且我们是求和­区间求和?­前缀和!­令sum[step][i]表示f[ste ...

  10. PHP: Local 和 Global 作用域

    函数之外声明的变量拥有 Global 作用域,只能在函数以外进行访问. 函数内部声明的变量拥有 LOCAL 作用域,只能在函数内部进行访问. 下面的例子测试了带有局部和全局作用域的变量: 在上例中,有 ...