HashMap+双向链表手写LRU缓存算法/页面置换算法
import java.util.Hashtable;
class DLinkedList {
String key; //键
int value; //值
DLinkedList pre; //双向链表前驱
DLinkedList next; //双向链表后继
}
public class LRUCache {
private Hashtable<String,DLinkedList> cache = new Hashtable<String,DLinkedList>();
private int count;
private int capacity;
private DLinkedList head, tail;
public LRUCache(int capacity) {
this.count = 0;
this.capacity = capacity;
head = new DLinkedList();
head.pre = null;
tail = new DLinkedList();
tail.next = null;
head.next = tail;
tail.pre = head;
}
public int get(String key) {
DLinkedList node = cache.get(key);
if(node == null) return -1;
this.moveToHead(node);
return node.value;
}
public void set(String key,int value) {
DLinkedList node = cache.get(key);
if(node == null) {
DLinkedList newNode = new DLinkedList();
newNode.key = key;
newNode.value = value;
this.cache.put(key, newNode);
this.addNode(newNode);
++count;
if(count>capacity) {
DLinkedList tail = this.popTail();
this.cache.remove(tail.key);
--count;
}
}
else {
node.value = value;
this.moveToHead(node);
}
}
private void addNode(DLinkedList node) {
node.pre = head;
node.next = head.next;
head.next.pre = node;
head.next = node;
}
private void removeNode(DLinkedList node) {
DLinkedList pre = node.pre;
DLinkedList next = node.next;
pre.next = next;
next.pre = pre;
}
private void moveToHead(DLinkedList node) {
this.removeNode(node);
this.addNode(node);
}
private DLinkedList popTail() {
DLinkedList res = tail.pre;
this.removeNode(res);
return res;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
DLinkedList node = head;
while(node != null){
sb.append(String.format("%s:%s ", node.key,node.value));
node = node.next;
}
return sb.toString();
}
public static void main(String[] args) {
LRUCache lru = new LRUCache(3);
lru.set("1", 7);
System.out.println(lru.toString());
lru.set("2", 0);
System.out.println(lru.toString());
lru.set("3", 1);
System.out.println(lru.toString());
lru.set("4", 2);
System.out.println(lru.toString());
lru.get("2");
System.out.println(lru.toString());
lru.set("5", 3);
System.out.println(lru.toString());
lru.get("2");
System.out.println(lru.toString());
lru.set("6", 4);
System.out.println(lru.toString());
/*
0ull:0 1:7 null:0
null:0 2:0 1:7 null:0
null:0 3:1 2:0 1:7 null:0
null:0 4:2 3:1 2:0 null:0
null:0 2:0 4:2 3:1 null:0
null:0 5:3 2:0 4:2 null:0
null:0 2:0 5:3 4:2 null:0
null:0 6:4 2:0 5:3 null:0
*/
}
}
那么如何设计一个LRU缓存,使得放入和移除都是 O(1) 的,我们需要把访问次序维护起来,但是不能通过内存中的真实排序来反应,有一种方案就是使用双向链表。
整体的设计思路是,可以使用 HashMap 存储 key,这样可以做到 save 和 get key的时间都是 O(1),而 HashMap 的 Value 指向双向链表实现的 LRU 的 Node 节点,如图所示。

LRU 存储是基于双向链表实现的,下面的图演示了它的原理。其中 head 代表双向链表的表头,tail 代表尾部。首先预先设置 LRU 的容量,如果存储满了,可以通过 O(1) 的时间淘汰掉双向链表的尾部,每次新增和访问数据,都可以通过 O(1)的效率把新的节点增加到对头,或者把已经存在的节点移动到队头。
下面展示了,预设大小是 3 的,LRU存储的在存储和访问过程中的变化。为了简化图复杂度,图中没有展示 HashMap部分的变化,仅仅演示了上图 LRU 双向链表的变化。我们对这个LRU缓存的操作序列如下:
save("key1", 7)
save("key2", 0)
save("key3", 1)
save("key4", 2)
get("key2")
save("key5", 3)
get("key2")
save("key6", 4)
相应的 LRU 双向链表部分变化如下:
s = save, g = get
总结一下核心操作的步骤:
- save(key, value),首先在 HashMap 找到 Key 对应的节点,如果节点存在,更新节点的值,并把这个节点移动队头。如果不存在,需要构造新的节点,并且尝试把节点塞到队头,如果LRU空间不足,则通过 tail 淘汰掉队尾的节点,同时在 HashMap 中移除 Key。
- get(key),通过 HashMap 找到 LRU 链表节点,因为根据LRU 原理,这个节点是最新访问的,所以要把节点插入到队头,然后返回缓存的值。
【https://zhuanlan.zhihu.com/p/34133067】
HashMap+双向链表手写LRU缓存算法/页面置换算法的更多相关文章
- 页面置换算法 - FIFO、LFU、LRU
缓存算法(页面置换算法)-FIFO. LFU. LRU 在前一篇文章中通过leetcode的一道题目了解了LRU算法的具体设计思路,下面继续来探讨一下另外两种常见的Cache算法:FIFO. LFU ...
- 操作系统-2-存储管理之LRU页面置换算法(LeetCode146)
LRU缓存机制 题目:运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制. 它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - ...
- 操作系统笔记(六)页面置换算法 FIFO法 LRU最近最久未使用法 CLOCK法 二次机会法
前篇在此: 操作系统笔记(五) 虚拟内存,覆盖和交换技术 操作系统 笔记(三)计算机体系结构,地址空间.连续内存分配(四)非连续内存分配:分段,分页 内容不多,就不做index了. 功能:当缺页中断发 ...
- 操作系统页面置换算法(opt,lru,fifo,clock)实现
选择调出页面的算法就称为页面置换算法.好的页面置换算法应有较低的页面更换频率,也就是说,应将以后不会再访问或者以后较长时间内不会再访问的页面先调出. 常见的置换算法有以下四种(以下来自操作系统课本). ...
- 操作系统 页面置换算法LRU和FIFO
LRU(Least Recently Used)最少使用页面置换算法,顾名思义,就是替换掉最少使用的页面. FIFO(first in first out,先进先出)页面置换算法,这是的最早出现的置换 ...
- (待续)C#语言中的动态数组(ArrayList)模拟常用页面置换算法(FIFO、LRU、Optimal)
目录 00 简介 01 算法概述 02 公用方法与变量解释 03 先进先出置换算法(FIFO) 04 最近最久未使用(LRU)算法 05 最佳置换算法(OPT) 00 简介 页面置换算法主要是记录内存 ...
- 页面置换算法(最佳置换算法、FIFO置换算法、LRU置换算法、LFU置换算法)
页面置换产生的原因是:分页请求式存储管理(它是实现虚拟存储管理的方法之一,其中一个特性是多次性-->多次将页面换入或换出内存) 效果最好的页面置换算法:最佳置换算法 比较常用的页面置换算法有:F ...
- 页面置换算法-LRU(Least Recently Used)c++实现
最近最久未使用(LRU)置换算法 #include <iostream> #include <cstdio> #include <cstring> #include ...
- 页面置换算法之Clock算法
1.前言 缓冲池是数据库最终的概念,数据库可以将一部分数据页放在内存中形成缓冲池,当需要一个数据页时,首先检查内存中的缓冲池是否有这个页面,如果有则直接命中返回,没有则从磁盘中读取这一页,然后缓存到内 ...
随机推荐
- Golang入门教程(一)GOPATH与工作空间(Windows)
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.2.md Windows 环境: 下面我就 ...
- Mac下crontab定时python任务
1.新建crontab_file vim输入代码*/ * * * * /Library/Frameworks/Python.framework/Versions/3.6/bin/python3 /Us ...
- Java并发编程原理与实战六:主线程等待子线程解决方案
本文将研究的是主线程等待所有子线程执行完成之后再继续往下执行的解决方案 public class TestThread extends Thread { public void run() { Sys ...
- [大数据测试]ETL测试或数据仓库测试入门
转载自: http://blog.csdn.net/zhusongziye/article/details/78633934 概述 在我们学习ETL测试之前,先了解下business intellig ...
- Presto通过RESTful接口新增Connector
在实际使用Presto的过程中,经常会有以下的一些需求. 添加一个新的Catalog 对不再使用的Catalog希望把它删除 修改某个Catalog的参数 但在Presto中如果进行上述的修改,需要重 ...
- 【BZOJ】3786: 星系探索
[题意]给定一棵带点权树,三种操作: 1.询问点x到根的路径和 2.子树x内的点权加定值y 3.将点x的父亲更换为y,保证仍是树. [算法]平衡树(fhq-treap) [题解] 将树的dfs序作为序 ...
- iOS6下实现滑动返回
[转载请注明出处] 之前在看iOS7滑动返回时,发现了一个iOS6 SDK下的第三方实现,今天偶然间发现了作者在其博客上对该实现的一些心得,读来深觉之前的思考太过肤浅,许多实际的问题没有考虑到.帖子链 ...
- 数位DP入门(A - 不要62 HDU - 2089 &&B - Bomb HDU - 3555 )
题目链接:https://cn.vjudge.net/contest/278036#problem/A 具体思路:对于给定的数,我们按照位数进行运算,枚举每一位上可能的数,在枚举的时候需要注意几个条件 ...
- jQuery选择器——(三)
1.基本元素选择器 id选择器:$(“#id名称”); 元素选择器:$(“元素名称”); 类选择器:$(“.类名”); 通配符:* 多个选择器共用(并集) 2.层级选择器 ancestor desce ...
- 【连接查询】mySql多表连接查询与union与union all用法
1.准备两个表 表a: 结构: mysql> desc a; +-------+-------------+------+-----+---------+-------+ | Field | T ...