LeetCode之LRU Cache 最近最少使用算法 缓存设计
设计并实现最近最久未使用(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 最近最少使用算法 缓存设计的更多相关文章
- [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- [LeetCode] 146. LRU Cache 近期最少使用缓存
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- 146 LRU Cache 最近最少使用页面置换算法
设计和实现一个 LRU(最近最少使用)缓存 数据结构,使它应该支持以下操作: get 和 put .get(key) - 如果密钥存在于缓存中,则获取密钥的值(总是正数),否则返回 -1.put(k ...
- [LeetCode] LRU Cache 最近最少使用页面置换缓存器
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- LeetCode OJ:LRU Cache(最近使用缓存)
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- 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 解决报告
插话:只写了几个连续的博客,博客排名不再是实际"远在千里之外"该.我们已经进入2一万内. 再接再厉.油! Design and implement a data structure ...
- leetcode 146. LRU Cache 、460. LFU Cache
LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面,如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目. LFU ...
- leetcode 146. LRU Cache ----- java
esign and implement a data structure for Least Recently Used (LRU) cache. It should support the foll ...
随机推荐
- Omnet++ 4.0 入门实例教程
http://blog.sina.com.cn/s/blog_8a2bb17d01018npf.html 在网上找到的一个讲解omnet++的实例, 是4.0下面实现的. 我在4.2上试了试,可以用. ...
- Nginx 配置文件详解
user nginx ; #用户 worker_processes 8; #工作进程,根据硬件调整,大于等于cpu核数 error_log logs/nginx_error.log crit; #错误 ...
- eq相等 ne、neq不相等, gt大于, lt小于 gte、ge大于等于 lte、le 小于等于 not非 mod求模 等
eq相等 ne.neq不相等, gt大于, lt小于 gte.ge大于等于 lte.le 小于等于 not非 mod求模 is [not] div by是否能被某数整除 i ...
- 新浪微博客户端(14)-截取回调地址中的授权成功的请求标记,换取access_token
DJOAuthViewController.m #import "DJOAuthViewController.h" #import "AFNetworking.h&quo ...
- C#编程总结 dynamic(转)
介绍 Visual C# 2010 引入了一个新类型 dynamic. 该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类型检查. 大多数情况下,该对象就像具有类型 object 一样 ...
- CF440C
C. One-Based Arithmetic time limit per test 0.5 seconds memory limit per test 256 megabytes input st ...
- 深入浅出JSON
JSON定义 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成.它基于ECMA262语言规范(1999 ...
- Activity切换后,如i何保存上一个Activit的状态
在Activity切换中一般有三种方式保存上一个Activity的状态数据.一.全局变量 public static int type = 0;二.SharedPreference 保 ...
- xocde真机测试 内存查看
如上, 有的时候真机调试, 内存和cpu占用没有被展示出来, 那么真机测试的时候怎么查看我们当前使用的内存呢, 有办法: instrument->activity monitory 点击左上角的 ...
- HDOJ 3790
dijstra最短路径算法 : 9885560 2013-12-23 23:54:56 Accepted 3790 203MS 8112K 1343 B C++ 泽泽 #include<cstd ...