一、简介
LRU是Least Recently Used的缩写,即最近最少使用页面置换算法,是为虚拟页式存储管理服务的,是根据页面调入内存后的使用情况进行决策了。LRU算法就是将最近最久未使用的页面予以淘汰。
操作系统里,在内存不够的场景下,淘汰旧内容的策略。在计算机体系里,最大最可靠的存储是硬盘,容量大、内容可固化,但访问速度慢,所以需要将适用的内容载入内存。内存速度快但容量有限,新的内容被载入,旧的内容就要被淘汰。
二、原理
方案1、栈
用一个栈来表示正在使用的页面的页面号,当一个新的进程访问某页面时,便将该页面号压入栈顶,其他的页面号往栈底移,如果内存不够,则将栈底的页面号移除。这样,栈顶始终是最新被访问的页面的编号,而栈底则是最近最久未访问的页面的页面号
这样设计可能问题很多,这段内存按照访问时间进行了排序,会有大量的内存拷贝操作,所以性能肯定是不能接受的。
设计一个LRU缓存,使得放入和移除都是 O(1) 的,我们需要把访问次序维护起来,但是不能通过内存中的真实排序来反应,有一种方案就是哈希双向链表(方案2)。
方案2、哈希链表
哈希表存储的是页面的key-value,哈希链表中的key-value被一个链条串起来,每一个Key-Value都具有它的前驱Key-Value、后继Key-Value,就像双向链表中的节点一样。这样一来,原本无序的哈希表拥有了固定的排列顺序。
使用 HashMap 存储 key,这样可以做到 save 和 get key的时间都是 O(1),而 HashMap 的 Value 指向双向链表实现的 LRU 的 Node 节点
步骤:
- save(key, value),首先在 HashMap 找到 Key 对应的节点,如果节点存在,更新节点的值,并把这个节点移动队头。如果不存在,需要构造新的节点,并且尝试把节点塞到队头,如果LRU空间不足,则通过 tail 淘汰掉队尾的节点,同时在 HashMap 中移除 Key。
- get(key),通过 HashMap 找到 LRU 链表节点,把节点插入到队头,返回缓存的值。
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.cap = capacity;
this.cache = new Map();
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
if (!this.cache.has(key)) {
return -1;
}
// 将 key 变为最近使用
this.makeRecently(key);
return this.cache.get(key);
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, val) {
if (this.cache.has(key)) {
// 修改 key 的值
this.cache.set(key, val);
// 将 key 变为最近使用
this.makeRecently(key);
return;
}
if (this.cache.size >= this.cap) {
// 链表头部就是最久未使用的 key
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
// 将新的 key 添加链表尾部
this.cache.set(key, val);
};
LRUCache.prototype.makeRecently = function(key) {
const val = this.cache.get(key);
// 删除 key,重新插入到队尾
this.cache.delete(key);
this.cache.set(key, val);
};
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/