LFU -- Javascript实现版本

Posted 奋飛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LFU -- Javascript实现版本相关的知识,希望对你有一定的参考价值。

LFU(Least Frequently Used)

LFU 最近最不常用,是一种常见的淘汰(置换)算法,选择最近使用次数最少的予以淘汰。常用于内存管理。

对每个块进行引用计数,当缓存达到容量上限并有新的模块插入时,系统将搜索具有最低计数的块删除。

算法实现:双向链表 + 哈希表

  1. 节点:Node key, value, freq, pre, post

    • key、value
    • freq:使用频率, 删除时使用
    • pre、post: 前置及后置节点,插入使用
  2. 双向链表:DualLinkedList head, tail, remove(), add(), isEmpty()

    按照被使用的顺序存储 node,头部head为最近使用的,尾部tail是最久未使用的。

    • head、tail:头及尾节点
    • remove():移除节点
    • add():添加节点(追加到 head 节点之后)
    • isEmpty():链表是否为空
  3. 定义 Hash 表:

    • freqMap :以频率(freq)为索引,每个索引对应存放一个双向链表,链表中存放所有改频率的节点(key,value,freq
    • cacheMap: 以 key 为索引,每个索引对应存放相应的 node 节点
/* 定义节点 */
class Node 
    constructor (key, value) 
        this.key = key
        this.value = value
        this.freq = 1   	// 访问频次
        this.pre = null
        this.post = null
    


/* 双向链 表*/
class DualLinkedList 
    constructor (node) 
        this.head = new Node()
        this.tail = new Node()
        this.head.post = this.tail
        this.tail.pre = this.head
    

    /* 移除 */
    remove (node) 
        node.pre.post = node.post
        node.post.pre = node.pre
    

    /* 插入节点(head之后) */
    add (node) 
        node.post = this.head.post
        this.head.post.pre = node
        node.pre = this.head
        this.head.post = node
    

    /* 判空 */
    isEmpty () 
        return this.head.post === this.tail && this.tail.pre === this.head
    


class LFUCache 
    constructor (capacity) 
        this.capacity = capacity
        this.minFreq = 0  				// 最小使用频率,删除使用
        this.size = 0     				// 当前已使用的容量
        this.freqMap = new Map()  // freq-DualLinkedList
        this.cacheMap = new Map() // key-node
    

    // 频率+1,freqMap的旧频率的key中移除,添加到新的freq链表中
    increaseFreq (node) 
        let list = this.freqMap.get(node.freq)
        list.remove(node)
      	// 同步更新最新频率标记(如果当前是最小频率节点)
        if (list.isEmpty() && node.freq === this.minFreq) 
            this.minFreq += 1
        
        node.freq += 1
        let newList = this.freqMap.get(node.freq)
        if (newList === undefined) 
            newList = new DualLinkedList()
        
        newList.add(node)	// 最新频率索引对应的双向链表中增加该节点
        this.freqMap.set(node.freq, newList)
    
 
  	/* 获取节点 value */
    get(key) 
        let node = this.cacheMap.get(key)
        if (node === undefined) 
          	// 不存在,返回 -1
            return -1
        
      	// 存在,返回当前节点值;同时访问频次 +1
        this.increaseFreq(node)
        return node.value
    

   	/* 增加节点 */ 	
    put (key, value) 
        if (this.capacity === 0) return
        // 如果key已存在,则变更其值,增加访问频率
        if (this.cacheMap.has(key)) 
            let node = this.cacheMap.get(key)
            node.value = value
            this.cacheMap.set(key, node)
            this.increaseFreq(node)
            return
        
      	// 容量达到上限
        if (this.size === this.capacity) 
          	// 获取最小频率队列(通过minFreq标记)
            let miniFreqList = this.freqMap.get(this.minFreq)
            // 获取最小频次队列的队尾(最小且最久)
            let miniFreqNode = miniFreqList.tail.pre
            // 淘汰该节点
            miniFreqList.remove(miniFreqNode)
            this.freqMap.set(this.minFreq, miniFreqList)
            this.cacheMap.delete(miniFreqNode.key)
        

      	// 插入新节点
        let node = new Node(key, value)
        this.cacheMap.set(key, node)
    		// freq为1的链表中,插入节点
        let list = this.freqMap.get(1)
        if (list === undefined) 
            list = new DualLinkedList()
        
        list.add(node)

        this.freqMap.set(1, list)
        this.minFreq = 1
        if (this.size < this.capacity) 
            this.size += 1
        
        return
    

复杂度:

  • 时间复杂度:对于 putget 都是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( c a p a c i t y ) O(capacity) O(capacity),因为哈希表和双向链表最多存储 $ capacity+1 $ 个元素。

借助 map 的 LRU 特性

map.delete(key)map.set(key, value) 则 map 对应的key被移到了尾部。

1、Let M be the this value.

7、Append p as the last element of entries.
8、Return M.

Map.prototype.set

/**
 * 定义节点
 */
class Node 
    constructor (key, value) 
        this.key = key
        this.value = value
        this.freq = 1   // 访问频次
    


class LFUCache 
    constructor (capacity) 
        this.capacity = capacity
        this.minFreq = 0  // 最小使用频率,删除使用
        this.size = 0     // 当前已使用的容量
        this.freqMap = new Map()  // 频率-map<node>(不使用双向链表)
        this.cacheMap = new Map() // key-node(用于get方法,快捷获取相对应key的value值)
    

    /**
     * 频率+1
     * ① 从freqMap的旧频率的key中移除
     * ② 旧频率为空 && 当前频率是最小频率,需要将最小频率+1
     * ③ 添加到新频率(旧频率+1)对应的map队列中 => 当前队列没有需要新建
     */
    increaseFreq (node) 
        let map = this.freqMap.get(node.freq)
        map.delete(node.key)
        if (!map.size && node.freq === this.minFreq) 
            this.minFreq += 1
        
        node.freq += 1
        let newMap = this.freqMap.get(node.freq)
        if (newMap === undefined) 
            newMap = new Map()
        
        newMap.set(node.key, node)
        this.freqMap.set(node.freq, newMap)
    

  	// 借助缓存的 cacheMap 处理
    get(key) 
        let node = this.cacheMap.get(key)
        if (node === undefined) 
            return -1
        
        this.increaseFreq(node)
        return node.value
    

    put (key, value) 
        if (this.capacity === 0) return
        
        // 如果节点已存在,则变更其值
        if (this.cacheMap.has(key)) 
            let node = this.cacheMap.get(key)
            node.value = value
            this.cacheMap.set(key, node)
            this.increaseFreq(node)
            return
        
      	// 达到最大容量,需要删除访问频次最小&&最久的节点
        // 访问频次最小=>通过minFreq标记获取相应的map
        // map中第一个元素为最久未被访问元素(map lru特性决定)
        if (this.size === this.capacity) 
            let miniFreqMap = this.freqMap.get(this.minFreq)

            let miniFreqNode = miniFreqMap.get(miniFreqMap.keys().next().value)    // 频次最小,第一个key(借助map的lru特性)
            miniFreqMap.delete(miniFreqNode.key)
            this.freqMap.set(this.minFreq, miniFreqMap)
            this.cacheMap.delete(miniFreqNode.key)
        

      	// 增加节点元素(这里只处理节点不存在,已存在上面已处理)
        let node = new Node(key, value)
        this.cacheMap.set(key, node)
        let map = this.freqMap.get(1)
        if (map === undefined) 
            map = new Map()
        
        map.set(node.key, node)
				// 对应新节点,访问频次一定是1,且最小频次为1
        this.freqMap.set(1, map)
        this.minFreq = 1
        if (this.size < this.capacity) 
            this.size += 1
        
        return
    

复杂度:

  • 时间复杂度:对于 putget 都是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( c a p a c i t y ) O(capacity) O(capacity)

以上是关于LFU -- Javascript实现版本的主要内容,如果未能解决你的问题,请参考以下文章

吃透Redis:缓存淘汰篇-LFU算法

吃透Redis:缓存淘汰篇-LFU算法

吃透Redis:缓存淘汰篇-LFU算法

详解三种缓存过期策略LFU,FIFO,LRU(附带实现代码)

html PHP代码片段: - AJAX基本示例:此代码演示了使用PHP和JavaScript实现的基本AJAX功能。

JavaScript - 代码片段,Snippets,Gist