- 浏览器缓存
当我们打开一个网页时,例如 https://github.com/sisterAn/JavaScript-Algorithms ,它会在发起真正的
网络请求前,查询浏览器缓存,看是否有要请求的文件,如果有,浏览器将会拦截请求,返回缓存文件,并直接结
束请求,不会再去服务器上下载。如果不存在,才会去服务器请求。
其实,浏览器中的缓存是一种在本地保存资源副本,它的大小是有限的,当我们请求数过多时,缓存空间会被用满,
此时,继续进行网络请求就需要确定缓存中哪些数据被保留,哪些数据被移除,这就是浏览器缓存淘汰策略,最常
见的淘汰策略有 FIFO(先进先出)、LFU(最少使用)、LRU(最近最少使用)。
LRU ( Least Recently Used :最近最少使用 )缓存淘汰策略,故名思义,就是根据数据的历史访问记录来进
行淘汰数据,其核心思想是 如果数据最近被访问过,那么将来被访问的几率也更高 ,优先淘汰最近没有被访问到的数据。
- vue中的keep-alive
keep-alive 在 vue 中用于实现组件的缓存,当组件切换时不会对当前组件进行卸载。
keep-alive 新增了 max 属性,用于最多可以缓存多少组件实例,一旦这个数字达到了,在新实例被创建之前,
已缓存组件中最久没有被访问的实例会被销毁掉,这里就应用了 LRU 算法。即在 keep-alive 中缓存达到
max,新增缓存实例会优先淘汰最近没有被访问到的实例
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。
它应该支持以下操作: 获取数据 get 和写入数据 put 。
获取数据 get(key) - 如果密钥 ( key ) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1 。
写入数据 put(key, value) - 如果密钥不存在,则写入数据。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据,从而为新数据留出空间
你是否可以在 O(1) 时间复杂度内完成这两种操作?
/*
LRUCache cache = new LRUCache( 2 ) //缓存容量
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
*/
function LRUCache(max) {
this.cache = {}; //实现O(1)时间复杂度,用对象存放数据,方便查找
this.keys=[]; //用数组存放key,保存访问顺序
this.max = max;
}
LRUCache.prototype.put = function(key, value) {
// 先看keys中是否有key,如果有,需要删除(因为要作为新访问的元素重新加入)
if(this.cache[key] !== undefined) {
const index = this.keys.indexOf(key)
this.keys.splice(index, 1)
}
// 若已是最大容量,先删除keys中第一个元素
if(this.keys.length === this.max) {
const removeKey = this.keys[0]
delete this.cache[removeKey]
this.keys.shift()
}
// 加入key
this.cache[key] = value
this.keys.push(key) //加入数组尾部
}
LRUCache.prototype.get = function(key) {
// 如果存在key,删除keys中的key,再把key作为最新访问重新加入,即放入keys尾部
if(this.cache[key] !== undefined) {
const index = this.keys.indexOf(key)
this.keys.splice(index, 1)
this.keys.push(key)
return this.cache[key]
}
return -1
}
cache = new LRUCache(2)
cache.put(1, 1); //(1)
cache.put(2, 2); //(2)
console.log(cache.get(1)); //(3)
cache.put(3, 3); //(4)
console.log(cache.get(2)); //(5)
cache.put(4, 4); //(6)
cache.put(3, 3); //(7)
console.log(cache.get(1)); //(8)
cache.put(3, 3); //(9)
console.log(cache.get(3)); //(10)
cache.put(3, 3); //(11)
console.log(cache.get(4)); //(12)
- 用Map实现 用Map实现就不需要用数组存放key来保存访问顺序了,因为Map能够记住键的原始插入顺序,map.keys()是按key的插入顺序返回的
function LRUCache(max) {
this.cache = new Map()
this.max = max
}
LRUCache.prototype.put = function(key, value) {
if(this.cache.has(key)) {
this.cache.delete(key)
}
if(this.cache.size === this.max) {
this.cache.delete(this.cache.keys().next().value) //this.cache.keys().next().value获取map第一个元素的key
}
this.cache.set(key, value)
}
LRUCache.prototype.get = function(key) {
if(this.cache.has(key)) {
const value = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, value)
return value
}
return -1
}
在leetcode中使用Map要比使用Object的性能好很多,通过查看MDN得知Map'在频繁增删键值对的场景下表现更好',
而Object'在频繁添加和删除键值对的场景下未作出优化'
下图截自MDN:
- 用双向链表实现
function LinkNode(value) {
this.value = value;
this.pre = null;
this.next = null;
}
function LRUCache(max) {
this.head = null; //链表头
this.tail = null; // 链表尾
this.number = 0; //已有节点个数
this.max = max; //缓存最大容量
}
// 先查找节点是否存在,如果存在则摘下放入头部;
// 不存在则先直接放到头部,如果链表满了,还需要删除尾部节点
LRUCache.prototype.put = function (key, value) {
const node = new LinkNode({ [key]: value });
if(this.find(key)) {
return
}
// 链表还是空的
if (!this.number) {
this.head = node;
this.tail = node;
this.number++;
return;
}
// 链表已经满了, 删掉最后面的节点
if (this.number === this.max) {
this.tail = this.tail.pre;
this.tail.next = null;
this.number --;
}
//把新节点加入头部
node.next = this.head;
this.head.pre = node;
this.head = node;
this.number++;
};
// 查找节点是否存在,存在则把节点放入头部,返回该节点;不存在则返回Null
LRUCache.prototype.find = function(key) {
// 查找节点
let cur = this.head;
while (cur && !cur.value[key]) {
cur = cur.next;
}
// 没找到
if (!cur) {
return null;
}
// 找到的节点为头结点,直接返回
if(!cur.pre) {
return cur
}
// 找到的节点为头结点不是头结点,先把找到的节点摘掉
if(!cur.next) {
// 找到的节点是尾结点
this.tail = this.tail.pre;
this.tail.next = null
} else {
// 找到的节点不是尾结点
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
}
//把找到的节点放到头部
cur.next = this.head;
this.head.pre = cur;
this.head = cur;
this.head.pre = null;
return cur
}
LRUCache.prototype.get = function (key) {
const node = this.find(key)
return node ? node.value[key] : -1;
};