从浏览器缓存淘汰策略和Vue的keep-alive学习LRU算法

本文介绍了LRU(最近最少使用)算法原理及其在浏览器缓存策略中的应用,重点讲解了Vue的keep-alive组件如何利用LRU策略实现组件缓存。通过分析源码,展示了keep-alive如何在缓存达到最大限制时淘汰最近未使用的组件,并提供了LRU缓存机制的基础实现和进阶实现。
摘要由CSDN通过智能技术生成

从浏览器缓存淘汰策略和vue的keep-alive学习LRU算法

由浏览器的缓存策略引出LRU算法原理
然后走进Vue中keep-alive的应用
接着,透过vue中的keep-alive源码来看LRU算法的实现
最后 来一道leetCodo题目 我们来实现一个LRU算法

一.LRU缓存淘汰策略

缓存在计算机网络上随处可见 例如:当我们首次访问一个网页时 打开很慢,但当我们再次打开时,打开就很快
这就涉及缓存在浏览器上的应用:浏览器缓存。 当我们打开一个网页 它在发起真正的网络请求前,查询浏览器缓存,看是否有要请求的文件 如果有 浏览器将会拦截请求,
返回缓存文件 并直接结束请求,不会再去服务器上下载 如果不存在 才会去服务器请求
其实 浏览器中的缓存是一种在本地保存资源副本 它的大小是有限的,当我们请求数过多的时候 缓存空间会被用满
此时继续进行网络请求就需要确定缓存中哪些数据被保留 哪些数据被移除 这就是浏览器缓存淘汰策略,最常见的策略有FIFO(先进先出),LFU(最少使用)、LRU(最近最少使用)
LRU(Least Recently Used:最近最少使用) 缓存淘汰策略 顾名思义 就是根据数据的历史访问记录来淘汰数据
其核心思想是: 如果数据最近被访问过 那么将来被访问的几率也更高 优先淘汰最近没被访问到的数据

LRU 在keep-alive(Vue) 上的实现

keep-alive 在vue中用于实现组件的缓存 当组件切换时不会对当前组件进行卸载
基本

{/* <keep-alive>
    <component :is="view"></component>
</keep-alive> */}

最常用的两个属性:include、exclude 用于组件进行有条件的缓存,可以用逗号分隔字符串、正则表达式或一个数组来表示。
在2.5.0版本中,keep-alive 新增了max属性,用于最多可以缓存多少组件实例,一旦这个数字达到了,在新实例被创建之前,
以缓存组件中最久没有被访问的实例会被销毁掉,看 这里就应用了LRU算法。即在keep-alive 中缓存达到max,
新增缓存实例会优先淘汰最近没有被访问到的实例
下面我们透过vue源码看一下具体的实现

从vue源码看keep-alive的实现

export default {
    name:'keep-alive',
    //抽象组件属性,它在组件实例建立父子关系的时候会被忽略,发生在initLifecycle的过程中
    abstract:true,
    props:{
        //被缓存组件
        include:patternTypes,
        // 不被缓存组件
        exclude:patternTypes,
        // 指定缓存大小
        max:[String,Number]
    },
    created(){
        //初始化用于存储缓存的cache对象
        this.cache = Object.create(null)
        //初始化用于存储VNode key值的keys数组
        this.keys = []
    },
    destroyed() {
        for(const key in this.cache){
            // 删除所有缓存
            pruneCacheEntry(this.cache,key,this.keys);
        }
    },
    mounted(){
        // 监听缓存(include)/不缓存(exclude)组件的变化
        // 在变化时 重新调整cache
        // pruneCacheEntry:遍历cache 如果缓存的节点名称与传入的规则没有匹配上的话  就把这个节点从缓存中移除
        this.$watch('include',val=>{
            pruneCache(this,name => matches(val,name));
        });
        this.$watch('exclude',val=>{
            pruneCache(this,name=> !matches(val,name))
        })
    },
    render(){
        // 获取第一个子元素的 vnode
        const slot = this.$slot.default;
        const vnode:VNode = getFirstComponentChild(slot)
        const componentOptions:?VNodeComponentOptions = vnode && vnode.componentOPtions;        
        if(componentOptions){
            // name不在include中或者在exclude中直接返回vnode,否则继续进行下一步
            // check pattern
            const name:?string = getComponentName(componentOptions)
            const {include,exclude} = this
            if(
                //not included
                (include && (!name || !matches(include,name)))||
                //excluded
                (exclude &&name&&matches(exclude,name))

            ){
                return vnode
            }
            const {cache,keys} = this
            // 获取键,优先获取组件的name字段  否则是组件的tag
            const key:?string = vnode.key ===null?
            //same constructor may get registered as different local components
            // so cid alone is not enough
            componentOptions.Ctor.cid + (componentOptions.tag?`::${componentOptions.tag}`:""):vnode.key

            // 下面就是LRU算法了
            // 如果在缓存里有则调整
            // 没有 则放入(长度超过max,则淘汰最近没有访问的)
            // 如果命中缓存,则从缓存中获取vnode示例 并且调整key的顺序  放入keys数组的末尾 
            if(cache[key]){
                vnode.componentInstance = cache[key].componentInstance
                // make current key freshest
                remove(keys,key)
                keys.push(key)
            }//如果乜有命中缓存 就把vnode 放进缓存
            else {
                cache[key] = vnode
                keys.push(key)
                //prune oldest entry
                // 如果配置了max 并且缓存的长度超过了this.max 还要从缓存中删除第一个
                if(this.max && keys.length>parseInt(this.max)){
                    pruneCacheEntry(cache,keys[0],keys,this._vnode)
                }
            }
            // keepAlive标记位
            vnode.data.keepAlive = true
        }
        return vnode || (slot && slot[0])
    }
};
// 移除key缓存
function prunCacheEntry(
    cache:VNodeCache,
    key:String,
    keys:Array<string>,
    current?:VNode
){
    const cached = cache[key]
    if(cached &&(!current ||cached.tag !== current.tag)){
        cached.componentInstance.$destroy()
    }
    cache[key] = null
    remove(keys,key)
}
//  remove 方法(shared/util.js)
// Remove an item from an array
export function remove(arr:Array<any>,item:any):Array<any>|void{
    if(arr.length){
        const index = arr.indexOf(item)
        if(index>-1){
            return arr.slice(index,1)
        }
    }
}

https://github.com/vuejs/vue/blob/dev/src/core/components/keep-alive.js(keep-alive源码路径)
在keep-alive 缓存超过max时,使用的缓存淘汰算法就是LRU算法 它在实现的过程中用到了cache对象用于保存缓存的组件实例及key值,keys数组用于保存缓存组件的key
当keep-alive中渲染一个需要缓存的实例时:
判断缓存中是否已存在了该实例 缓存了则直接获取 并调整key在keys中的位置(移除keys中key,并放入keys数组的最后一位)
如果没有缓存 则缓存该实例 若keys的长度大于max (缓存长度超过上限) 则移除keys[0] 缓存

设计和实现一个LRU(最近最少使用)缓存机制

运用你所掌握的数据结构 设计和实现一个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)
caches.get(1) //返回1
caches.put(3,3)//改操作会使得秘钥2作废
caches.get(2) //返回-1(未找到)
caches.put(4,4) //该操作会使得秘钥1作废
caches.get(1) //返回-1(未找到)
caches.get(3) //返回3
caches.get(4) //返回4

基础解法:数组 + 对象实现
类 vue keep-alive 实现

var LRUCache = function(capacity){
    this.keys = []
    this.cache = Object.create(null)
    this.capacity = capacity
}
LRUCache.prototype.get = function(key){
    if(this.cache[key]){
        // 调整位置
        remove(this.keys,key)
        this.keys.push(key)
        return this.cache[key]
    }
    return -1
}
LRUCache.prototype.put = function(key,value){
    if(this.cache[key]){
        //存在即更新
      this.cache[key] = value
      remove(this.keys,key)
      this.keys.push(key)
    }else{
        // 不存在即加入
        this.keys.push(key)
        this.cache[key] = value
        // 判断缓存是否已超过最大值
        if(this.keys.length>this.capacity){
            removeCache(this.cache,this.keys,this.keys[0])
        }
    }
}
// 移除key
function remove(arr,key){
    if(arr.length){
        const index = arr.indexOf(key)
        if(index>-1){
            return arr.splice(index,1)
        }
    }
}

// 移除缓存中key
function removeCache(cache,keys,key){
    cache[key] = null
    remove(keys,key)
}
进阶
// 利用Map既能保存键值对 并且能够记住键的原始插入顺序
var LRUCache = function(capacity){
    this.cache = new Map()
    this.capacity = capacity
}
LRUCache.prototype.get = function(key){
    if(this.cache.has(key)){
        //存在即刷新
        let temp = this.cache.get(key)
        this.cache.delete(key)
        this.cache.set(key,temp)
        return temp
    }
    return -1
}

LRUCache.prototype.put = function(key,value){
    if(this.cache.has(key)){
        //存在即更新(删除后加入)
        this.cache.delete(key)
    }else if(this.cache.size >= this.capacity){
        // 不存在即加入
        // 缓存超过最大值 则移除最近没有使用的
        this.cache.delete(this.cache.keys().next().value)
    }
    this.cache.set(key,value)
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值