图解缓存淘汰算法 LRU、LFU | 最近最少使用、最不经常使用算法 | go语言实现

写在前面

无论是什么系统,在研发的过程中不可避免的会使用到缓存,而缓存一般来说我们不会永久存储,但是缓存的内容是有限的,那么我们如何在有限的内存空间中,尽可能的保留有效的缓存信息呢? 那么我们就可以使用 LRU/LFU算法 ,来维持缓存中的信息的时效性。

LRU 详解

原理

LRU (Least Recently Used:最近最少使用)算法在缓存写满的时候,会根据所有数据的访问记录,淘汰掉未来被访问几率最低的数据。也就是说该算法认为,最近被访问过的数据,在将来被访问的几率最大。

流程如下:
在这里插入图片描述
假设我们有这么一块内存,一共有26个数据存储块。

  1. 当我们连续插入A、B、C、…Z的时候,此时内存已经插满
  2. 那么当我们再插入一个6,那么此时会将内存存放时间最久的数据A淘汰掉。
  3. 当我们从外部读取数据C的时候,此时C就会提到头部,这时候C就是最晚淘汰的了。

其实流程来说很简单。我们来拆分一下的话,不难发现这就是在维护一个双向链表

代码实现

定义一个存放的数据块结构

type item struct {key   stringvalue any// the frequency of keyfreq int
}

定义LRU算法的结构体

type LRU struct {dl       *list.List // 维护的双端队列size     int // 当前的容量capacity int // 限定的容量storage map[string]*list.Element // 存储的key
}

获取某个key的value的函数,如果存在这个key,那么我们就把这个值移动到最前面MoveToFront,否则返回一个nil。

func (c *LRU) Get(key string) any {v, ok := c.storage[key]if ok {c.dl.MoveToFront(v)return v.Value.(item).value}return nil
}

当我们需要put进去一些东西的时候。会分以下几个步骤

  1. 是否已经存在,如果已经存在则,直接返回,并且将key移动到最前面。
  2. 如果没有存在,但是已经是到极限容量了,就把最后一个Back(),淘汰掉,然后在塞入。
  3. 塞入的话,是塞入到最前面PushFront
func (c *LRU) Put(key string, value any) {e, ok := c.storage[key]if ok {n := e.Value.(item)n.value = valuee.Value = nc.dl.MoveToFront(e)return}if c.size >= c.capacity {e = c.dl.Back()dk := e.Value.(item).keyc.dl.Remove(e)delete(c.storage, dk)c.size--}n := item{key: key, value: value}c.dl.PushFront(n)ne := c.dl.Front()c.storage[key] = nec.size++
}

以上就是LRU算法的所有内容了,那我们看一下LFU算法。

LFU

原理

LFU全称是最不经常使用算法(Least Frequently Used),LFU算法的基本思想和所有的缓存算法一样,一定时期内被访问次数最少的页,在将来被访问到的几率也是最小的。

相比于LRU(Least Recently Use)算法,LFU更加注重于使用的频率LRU是其实可以看作是频率为1的LFU的。

在这里插入图片描述

和LRU不同的是,LFU是根据频率排序的,当我们插入的时候,一般会把新插入的放到链表的尾部,因为新插入的一定是没有出现过的,所以频率都会是1 , 所以会放在最后。

所以LFU的插入顺序如下:

  1. 如果A没有出现过,那么就会放在双向链表的最后,依次类推,就会是Z、Y。。C、B、A的顺序放到频率为1的链表中。
  2. 当我们新插入 A,B,C 那么A,B,C就会到频率为2的链表中
  3. 如果再次插入A,B那么A,B会在频率为3中。C依旧在2中
  4. 如果此时已经满了 ,新插入一个的话,我们会把最后一个D移除,并插入 6

在这里插入图片描述

代码

定义一个LFU的结构体:

// LFU the Least Frequently Used (LFU) page-replacement algorithm
type LFU struct {len     int // lengthcap     int // capacityminFreq int // The element that operates least frequently in LFU// key: key of element, value: value of elementitemMap map[string]*list.Element// key: frequency of possible occurrences of all elements in the itemMap// value: elements with the same frequencyfreqMap map[int]*list.List // 维护一个频率和list的集合
}

我们使用LFU算法的话,我们插入的元素就需要带上频率了

// initItem to init item for LFU
func initItem(k string, v any, f int) item {return item{key:   k,value: v,freq:  f,}
}

如果我们获取某个元素,那么这个元素如果存在,就会对这个元素的频率进行加1

// Get the key in cache by LFU
func (c *LFU) Get(key string) any {// if existed, will return valueif e, ok := c.itemMap[key]; ok {// the frequency of e +1 and change freqMapc.increaseFreq(e)obj := e.Value.(item)return obj.value}// if not existed, return nilreturn nil
}

增加频率

// increaseFreq increase the frequency if element
func (c *LFU) increaseFreq(e *list.Element) {obj := e.Value.(item)// remove from low frequency firstoldLost := c.freqMap[obj.freq]oldLost.Remove(e)// change the value of minFreqif c.minFreq == obj.freq && oldLost.Len() == 0 {// if it is the last node of the minimum frequency that is removedc.minFreq++}// add to high frequency listc.insertMap(obj)
}

插入key到LFU缓存中

  1. 如果存在就对频率加1
  2. 如果不存在就准备插入
  3. 如果溢出了,就把最少频率的删除
  4. 如果没有溢出,那么就放到最后
// Put the key in LFU cache
func (c *LFU) Put(key string, value any) {if e, ok := c.itemMap[key]; ok {// if key existed, update the valueobj := e.Value.(item)obj.value = valuec.increaseFreq(e)} else {// if key not existedobj := initItem(key, value, 1)// if the length of item gets to the top line// remove the least frequently operated elementif c.len == c.cap {c.eliminate()c.len--}// insert in freqMap and itemMapc.insertMap(obj)// change minFreq to 1 because insert the newest onec.minFreq = 1// length++c.len++}
}

插入一个新的

// insertMap insert item in map
func (c *LFU) insertMap(obj item) {// add in freqMapl, ok := c.freqMap[obj.freq]if !ok {l = list.New()c.freqMap[obj.freq] = l}e := l.PushFront(obj)// update or add the value of itemMap key to ec.itemMap[obj.key] = e
}

找到最少的链表,并且删除

// eliminate clear the least frequently operated element
func (c *LFU) eliminate() {l := c.freqMap[c.minFreq]e := l.Back()obj := e.Value.(item)l.Remove(e)delete(c.itemMap, obj.key)
}

以上就是所有LFU的算法实现了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/544454.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm中如何使用不同的虚拟环境

1. 简介 有些项目用老的运行环境,而有些项目用新的运行环境,那么我们在运行这些代码(比如跑对比实验的时候)如何进行切换呢,这时候就可以使用虚拟环境啦 2. 虚拟环境的创建 首先启动Anaconda Prompt 并在其中执行如…

解决Linux中Eclipse启动时找不到Java环境的问题

按照报错的意思是没有在/usr/local/eclipse/jre/bin/java下找到java环境,我检查了一下eclipse的目录结构发现在/usr/local/eclipse没有jre/bin/java,我的想法是自己建对应文件夹然后软连接到我的java环境 cd /usr/local/eclipse sudo mkdir jre cd jre s…

Linux自动化任务管理以及常见定时命令示例

Linux以其强大的稳定性和灵活性成为了许多IT专业人士的首选。其中,自动化任务管理是Linux系统管理不可或缺的一部分,它能帮助系统管理员有效地管理系统任务,提高工作效率。定时任务,作为自动化任务管理的重要组成部分,…

嵌入式硬件设计(一)|利用 NodeMCU-ESP8266 开发板和继电器结合APP“点灯•blinker”制作Wi-Fi智能开关(附有关硬件详细资料)

概述 本文主要讲述利用 NodeMCU-ESP8266 开发板和继电器通过手机 APP “ 点灯 • Blinker ” 制作一款能够由手机控制的WiFi 智能开关,从而实现智能物联。NodeMCU 是基于 Lua 的开源固件,ESP8266-NodeMCU是一个开源硬件开发板,支持WiFi功能&a…

uploads-labs靶场(1-10关)

一、搭建环境: 下载upload-labs源代码 下载链接:https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master 将压缩包解压后的文件名改为upload-labs,然后放入phpstudy\www目录下 二、关卡通关: 1、pass-01(前端绕过&#xf…

VMware ESXi 8.0U1d macOS Unlocker OEM BIOS 集成网卡驱动和 NVMe 驱动 (集成驱动版)

VMware ESXi 8.0U1d macOS Unlocker & OEM BIOS 集成网卡驱动和 NVMe 驱动 (集成驱动版) 发布 ESXi 8.0U1 集成驱动版,在个人电脑上运行企业级工作负载 请访问原文链接:https://sysin.org/blog/vmware-esxi-8-u1-sysin/,查看最新版。原…

openlayers 入门教程(二):map 篇

还是大剑师兰特:曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,ech…

Java学习笔记(14)

常用API Java已经写好的各种功能的java类 Math Final修饰,不能被继承 因为是静态static的,所以使用方法不用创建对象,使用里面的方法直接 math.方法名 就行 常用方法 Abs,ceil,floor,round,max,minm,pow,sqrt,cbrt,random Abs要注意参数的…

「SpringBrick快速入门指南」:一款基于Spring Boot的高级插件化开发框架

文章目录 关于 | About技术文档 | Document开源项目 | Project 案例 | Demo项目结构 | Structure主程序配置集成 | Settings引入框架依赖 | Framework在配置文件加入配置 | YamlSpringBoot启动类改引导类 | Change 插件配置集成 | Settings引入依赖 | XML定义插件引导类 | Clas…

【计算机网络】https的工作原理以及和http的区别

目录 前言 1. HTTP协议存在的问题 2. 什么是HTTPS协议? 3. HTTP和HTTPS有哪些区别? 4. HTTPS的工作原理 加密方式 前言 在日常的Web项目练习中,我们会发现老师会让我们在打开服务器之后使用 http://localhost/...进行项目效果测试和预览…

Tuxera NTFS 2023安装使用教程 Tuxera NTFS破解版 Tuxera NTFS for Mac优惠

对于必须在Windows电脑和Mac电脑之间来回切换的Mac朋友来说,跨平台不兼容一直是一个巨大的障碍,尤其是当我们需要使用NTFS格式的硬盘在Windows和macOS之间共享文件时。因为Mac默认不支持写入NTFS磁盘。 为了解决这一问题,很多朋友会选择很便捷…

QML 布局管理器之ColumnLayout

一.ColumnLayout讲解 QML中的ColumnLayout是一种布局元素,用于在垂直列中排列其子元素。它的主要使用下列附加属性: Layout.minimumWidth Layout.minimumHeight Layout.preferredWidth Layout.preferredHeight Layout.maximumWidth Layout.maximumHeight Layout.fil…