学习周记(11.8-11.14)

163 阅读3分钟

LRU算法

  • 原题在Leetcode146题Vue中的keep-alive抽象组件中也用到了该算法

题目要求:

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1void put(int key, int value) 如果关键字已经存在,则变更其数据值;
如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,
它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
要求:在O(1)时间复杂度内完成这两种操作
  • 实现思路:因为涉及到key,value键值对,所以可以想到利用哈希表进行存储,而题目要求在容量达到上限后写入新数据会删除最久未使用的数据,所以每次操作都应该更新数据的最近使用顺序,在数据结构中,可以快速进行删除添加操作的数据结构可以想到链表。所以可以通过链表存储各个数据的使用顺序,用哈希表存储key和其数据在链表中对应的node。在JS中,没有内置的链表,不过可以用Map的特性实现链表的功能。代码如下:
/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
    this.map = new Map()
    this.capacity = capacity
    this.currentSiz = 0
};

/** 
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
    if (this.map.has(key)) {
        const res = this.map.get(key)
        this.map.delete(key)
        this.map.set(key, res)
        return res
    } else {
        return -1
    }
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
    if (this.map.has(key)) {
        this.map.delete(key)
        this.map.set(key, value)
    } else {
        this.map.set(key, value)
        if (this.currentSiz === this.capacity) {
            const k = this.map.keys().next().value
            this.map.delete(k)
        } else {
            this.currentSiz++
        }
    }
};

PS:不同于ObjectMap中的数据是有序的,所以可以通过先删除,再增加来更新数据的最近使用,
而map.keys()返回的是具有iterator接口的类数组结构,可以通过.next().value方法获取值,
也可将其转化为数组后获取第一个值,这第一个值便是最久未使用过的数据。上述的操作都是通过
map实现的,所以时间复杂度为O(1)

在Vue中的使用

  • 源码位置在'src/core/components/keep-alive.js',部分源码如下

// 修剪缓存:遍历当前缓存,对于不需要缓存的组件进行删除
function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

// 删除缓存中的组件,同时删除keys数组中的组件对应的key值
function pruneCacheEntry (
  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)
}

export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  created () {
    this.cache = Object.create(null)  // 存储组件
    this.keys = []  // 记录使用顺序
  },

// include或exclude数据改变时,更新内部缓存
  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this
      // 如果不需要缓存,直接返回vnode
      if (
        (include && (!name || !matches(include, name))) ||
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      const key: ?string = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      if (cache[key]) {
        // 如果已经在缓存中存在
        vnode.componentInstance = cache[key].componentInstance
        // 在这更新keys中的顺序,先删除,后增加
        remove(keys, key)
        keys.push(key)
      } else {
        // 如果缓存中不存在
        cache[key] = vnode
        keys.push(key)
        // 超过缓存上线后删除keys数组中最老的key对应的组件
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

本周总结:后续周记迁移到github