设计并实现最近最久未使用(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. 使用Jquery+EasyUI 进行框架项目开发案例讲解之五 模块(菜单)管理源码分享

    http://www.cnblogs.com/huyong/p/3454012.html 使用Jquery+EasyUI 进行框架项目开发案例讲解之五  模块(菜单)管理源码分享    在上四篇文章 ...

  2. c/c++细节知识整理

    这篇文章总结了部分c/c++琐碎的细节知识. 目录如下: (一)bool类型 知识点出处较多,无法一一列举,向原作者致敬. (一)bool类型 在c99标准以前,c语言并没有定义bool类型.如果需要 ...

  3. U盘中的autorun.inf

    怎么删除u盘里的autorun.inf 如果U盘中毒,刚插进机子时按住SHIFT五秒,这样就可以跳过预读,这样防止了预读时把病毒感染到机子上,在U盘盘符上点右键,看看有没有“Auto”选项: 1.如果 ...

  4. Java I/O操作

    按字节读取读取文件,并且将文件里面的内容写到另外一个文件里面去 public class CopyBytes { public static void main(String[] args) thro ...

  5. WPF 检测管理员权限

    // 检查是否是管理员身份 private static void CheckAdministrator() { WindowsIdentity wi = null; try { wi = Windo ...

  6. WCF 客户端解决大数据量传输配置

    1. 服务端返回数据给客户端报错 在客户端配置文件中,主要是配置maxReceivedMessageSize <system.serviceModel> <bindings> ...

  7. dos 下 查看和设置classpath的命令

    dos 下  查看和设置classpath的命令查看:echo %classpath%设置:set classpath=%classpath%path   其中%classpath%代表以前的值,pa ...

  8. Linux系统日志及日志分析

    Linux系统日志及日志分析   Linux系统拥有非常灵活和强大的日志功能,可以保存几乎所有的操作记录,并可以从中检索出我们需要的信息. 大部分Linux发行版默认的日志守护进程为 syslog,位 ...

  9. [Effective JavaScript 笔记]第30条:理解prototype、getPrototypeOf和__ptoto__之间的不同

    原型包括三个独立但相关的访问器.这三个单词都是对单词prototype做了一些变化. C.prototype用于建立由new C()创建的对象的原型 Object.getPrototypeOf(obj ...

  10. 计蒜客 X的平方根

    X的平方根 设计函数int sqrt(int x),计算x的平方根. 格式: 输入一个数x,输出它的平方根.直到碰到结束符号为止. 千万注意:是int类型哦- 输入可以如下操作: while(cin& ...