4

The last article introduced the LRU algorithm, and today I plan to introduce the LFU algorithm. As mentioned in the previous article, the LFU ( Least frequently used : least used) algorithm is different from the LRU algorithm only in the elimination strategy. LRU tends to retain recently used data, while LFU tends to retain more frequently used data. .

Take a simple 🌰: There are two data A and B in the cache, and the upper limit has been reached. If data A is accessed 10 times first, and then the data B is accessed once, when the new data C is stored , if the LRU algorithm is currently used, the data A will be eliminated, and if the LFU algorithm is used, the data B will be eliminated.

Simply put, in the LRU algorithm, regardless of the frequency of access, the data will not be eliminated as long as it has been accessed recently. In the LFU algorithm, the frequency of access is used as a weight. As long as the access frequency is higher, the data will be The more will not be eliminated, even if the data has not been accessed for a long time.

Algorithm implementation

We still implement this logic through a piece of JavaScript code.

class LFUCache {
    freqs = {} // 用于标记访问频率
    cache = {} // 用于缓存所有数据
    capacity = 0 // 缓存的最大容量
    constructor (capacity) {
    // 存储 LFU 可缓存的最大容量
        this.capacity = capacity
    }
}

Like the LRU algorithm, the LFU algorithm also needs to implement two methods, get and put , for obtaining and setting the cache.

class LFUCache {
  // 获取缓存
    get (key) { }
  // 设置缓存
    put (key, value) { }
}

The old rule, first look at the part of setting the cache. If the cached key existed before, its value needs to be updated.

class LFUCache {
  // cache 作为缓存的存储对象
  // 其解构为: { key: { freq: 0, value: '' } }
  // freq 表示该数据读取的频率;
  // value 表示缓存的数据;
    cache = {}
  // fregs 用于存储缓存数据的频率
  // 其解构为: { 0: [a], 1: [b, c], 2: [d] }
  // 表示 a 还没被读取,b/c 各被读取1次,d被读取2次
  freqs = {}
  // 设置缓存
  put (key, value) {
    // 先判断缓存是否存在
    const cache = this.cache[key]
    if (cache) {
      // 如果存在,则重置缓存的值
      cache.value = value
      // 更新使用频率
      let { freq } = cache
      // 从 freqs 中获取对应 key 的数组
      const keys = this.freqs[freq]
      const index = keys.indexOf(key)
      // 从频率数组中,删除对应的 key
      keys.splice(index, 1)
      if (keys.length === 0) {
        // 如果当前频率已经不存在 key
        // 将 key 删除
        delete this.freqs[freq]
      }
      // 更新频率加 1
      freq = (cache.freq += 1)
      // 更新频率数组
      const freqMap =
            this.freqs[freq] ||
            (this.freqs[freq] = [])
      freqMap.push(key)
      return
    }
  }
}

If the cache does not exist, first determine whether the cache exceeds the capacity. If it exceeds, the data with the least frequency needs to be eliminated.

class LFUCache {
  // 更新频率
  active (key, cache) {
    // 更新使用频率
    let { freq } = cache
    // 从 freqs 中获取对应 key 的数组
    const keys = this.freqs[freq]
    const index = keys.indexOf(key)
    // 从频率数组中,删除对应的 key
    keys.splice(index, 1)
    if (keys.length === 0) {
      // 如果当前频率已经不存在 key
      // 将 key 删除
      delete this.freqs[freq]
    }
    // 更新频率加 1
    freq = (cache.freq += 1)
    // 更新读取频率数组
    const freqMap = this.freqs[freq] || (this.freqs[freq] = [])
    freqMap.push(key)
  }
  // 设置缓存
  put (key, value) {
    // 先判断缓存是否存在
    const cache = this.cache[key]
    if (cache) {
      // 如果存在,则重置缓存的值
      cache.value = value
      this.active(key, cache)
      return
    }
    // 判断缓存是否超过容量
    const list = Object.keys(this.cache)
    if (list.length >= this.capacity) {
      // 超过存储大小,删除访问频率最低的数据
      const [first] = Object.keys(this.freqs)
      const keys = this.freqs[first]
      const latest = keys.shift()
      delete this.cache[latest]
      if (keys.length === 0) delete this.freqs[latest]
    }
    // 写入缓存,默认频率为0,表示还未使用过
    this.cache[key] = { value, freq: 0 }
    // 写入读取频率数组
    const freqMap = this.freqs[0] || (this.freqs[0] = [])
    freqMap.push(key)
  }
}

After implementing the method of setting the cache, it is easy to implement the obtaining cache.

class LRUCache {
  // 获取数据
    get (key) {
        if (this.cache[key] !== undefined) {
        // 如果 key 对应的缓存存在,更新其读取频率
      // 之前已经实现过,可以直接复用
            this.active(key)
            return this.cache[key]
        }
        return undefined
  }
}

The implementation of the LFU cache algorithm is here. Of course, the algorithm is generally implemented in the form of a double-linked list. The implementation here is just to facilitate understanding of its principle. If you are interested, you can search for a more efficient implementation on the Internet.


Shenfq
4k 声望6.8k 粉丝