golang中实现LRU-K算法(附带单元测试)

        LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。

        LRU-K具有LRU的优点,同时能够避免LRU的缺点,实际应用中LRU-2是综合各种因素后最优的选择,LRU-3或者更大的K值命中率会高,但适应性差,需要大量的数据访问才能将历史访问记录清除掉。

LRU-K 算法代码:

lru-k.go

package lruimport "container/list"// lru 缓存淘汰策略
// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {maxBytes int64 //允许使用的最大内存useBytes int64 //当前已使用的内存ll       *list.Listmp       map[string]*list.Element //键是字符串,值是双向链表中对应节点的指针// optional and executed when an entry is purged.OnEvicted    func(key string, value Value)historyCache HistoryCache // 历史队列,只有访问次数达到k次后才会加入到缓存中
}type HistoryCache struct {k        int // k次访问后加入缓存maxBytes int64useBytes int64ll       *list.List // 历史队列是以FIFO为淘汰策略mp       map[string]*list.Elementcnt      map[string]int // 对每个节点访问次数的统计
}type entry struct {key   stringvalue Value
}// Value use Len to count how many bytes it takes
type Value interface {Len() int
}// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value), k int) *Cache {return &Cache{maxBytes:  maxBytes,ll:        list.New(),mp:        make(map[string]*list.Element),OnEvicted: onEvicted,//将某个函数传递给 New 函数,并赋给 OnEvicted 字段,你可以在缓存中的条目被移除时执行自定义的操作,//比如释放资源、记录日志等,可以让 Cache 结构体更加通用和可扩展。historyCache: HistoryCache{k:        k, // 可以改为New()传入参,一般用2次命中率和适应性综合考虑最优maxBytes: maxBytes,ll:       list.New(),mp:       make(map[string]*list.Element),cnt:      make(map[string]int),},}
}// Get look ups a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {if _, ok = c.mp[key]; ok {// 缓存命中了就挪到前面ele := c.mp[key]c.ll.MoveToFront(ele)kv := ele.Value.(*entry)return kv.value, true} else {// 缓存未命中,去历史队列查看,如果访问次数达到k次需要加入到缓存中if _, ok = c.historyCache.mp[key]; ok {// 有就根据访问次数看是否要加到缓存中,没达到次数也要将该节点挪到最后,即最晚被FIFO淘汰c.historyCache.cnt[key]++ele := c.historyCache.mp[key]kv := ele.Value.(*entry)if c.historyCache.cnt[key] >= c.historyCache.k {c.AddToCache(key, value)// 加入缓存后,将该节点从历史队列中删除c.historyCache.ll.Remove(ele)c.historyCache.useBytes -= int64(kv.value.Len()) + int64(len(kv.key))delete(c.historyCache.mp, kv.key)delete(c.historyCache.cnt, kv.key)} else {c.historyCache.ll.MoveToBack(ele)}return kv.value, true} else {// 历史队列也没有就直接返回return}}return
}// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {if _, ok := c.mp[key]; ok {// 缓存命中了就挪到前面,更新valueele := c.mp[key]c.ll.MoveToFront(ele)kv := ele.Value.(*entry)c.useBytes += int64(value.Len()) - int64(kv.value.Len())kv.value = value} else {// 缓存未命中,则去历史队列查看是否存在if _, ok = c.historyCache.mp[key]; !ok {// 没有就新增ele := c.historyCache.ll.PushBack(&entry{key, value})c.historyCache.cnt[key]++c.historyCache.mp[key] = elec.historyCache.useBytes += int64(len(key)) + int64(value.Len())// 判断历史队列内存是否用完,历史队列的淘汰策略为FIFOif c.historyCache.maxBytes != 0 && c.historyCache.maxBytes < c.historyCache.useBytes {c.RemoveHistoryCacheOldest()}} else {// 有就更新value,并移到队尾c.historyCache.cnt[key]++ele := c.historyCache.mp[key]c.historyCache.ll.MoveToBack(ele)kv := ele.Value.(*entry)c.historyCache.useBytes += int64(value.Len()) - int64(kv.value.Len())kv.value = value}// 判断是否达到加入缓存标准if c.historyCache.cnt[key] >= c.historyCache.k {c.AddToCache(key, value)ele := c.historyCache.mp[key]kv := ele.Value.(*entry)// 加入缓存后,将该节点从历史队列中删除c.historyCache.ll.Remove(ele)c.historyCache.useBytes -= int64(kv.value.Len()) + int64(len(kv.key))delete(c.historyCache.mp, kv.key)delete(c.historyCache.cnt, kv.key)}}
}func (c *Cache) AddToCache(key string, value Value) {ele := c.ll.PushFront(&entry{key, value})c.mp[key] = elec.useBytes += int64(len(key)) + int64(value.Len())//保证内存不超过最大值 ps:maxBytes为0表示无限制for c.maxBytes != 0 && c.maxBytes < c.useBytes {c.RemoveCacheOldest()}
}// RemoveCacheOldest removes the oldest item
func (c *Cache) RemoveCacheOldest() {ele := c.ll.Back()if ele != nil {c.ll.Remove(ele)kv := ele.Value.(*entry)delete(c.mp, kv.key)c.useBytes -= int64(kv.value.Len()) + int64(len(kv.key))if c.OnEvicted != nil {c.OnEvicted(kv.key, kv.value)}}
}func (c *Cache) RemoveHistoryCacheOldest() {ele := c.historyCache.ll.Front()if ele != nil {c.historyCache.ll.Remove(ele)kv := ele.Value.(*entry)delete(c.historyCache.mp, kv.key)delete(c.historyCache.cnt, kv.key)c.historyCache.useBytes -= int64(kv.value.Len()) + int64(len(kv.key))if c.OnEvicted != nil {c.OnEvicted(kv.key, kv.value)}}
}// Len is the number of cache entries
func (c *Cache) Len() int {return c.ll.Len()
}

单元测试代码:

lru-k_test.go

package lruimport ("reflect""testing"
)type String stringfunc (d String) Len() int {return len(d)
}// 只针对于LRU的测试,即LRU-1func TestGet(t *testing.T) {lru := New(int64(0), nil, 1) // 0表示无限制lru.Add("key1", String("123"))if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "123" {t.Fatalf("cache hit key1=123 failed")}if _, ok := lru.Get("key2"); ok {t.Fatalf("cache miss key2 failed")}
}func TestRemoveOldest(t *testing.T) {k1, k2, k3 := "key1", "key2", "key3"v1, v2, v3 := "value1", "value2", "value3"cap := len(k1 + v1 + k2 + v2)lru := New(int64(cap), nil, 1)lru.Add(k1, String(v1))lru.Add(k2, String(v2))lru.Add(k3, String(v3))if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {t.Fatalf("RemoveOldest key1 failed")}
}func TestOnEvicted(t *testing.T) {keys := make([]string, 0)callback := func(key string, value Value) {keys = append(keys, key)}lru := New(int64(10), callback, 1)lru.Add("key1", String("123456"))lru.Add("k2", String("k2"))lru.Add("k3", String("k3"))lru.Add("k4", String("k4"))except := []string{"key1", "k2"}if !reflect.DeepEqual(except, keys) {t.Fatalf("Call OnEvicted failed, expect keys equals to %s", except)}}

参考:动手写分布式缓存 - GeeCache第一天 LRU 缓存淘汰策略 | 极客兔兔 (geektutu.com)

将LRU算法升级为了LRU-K算法

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

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

相关文章

Hadoop-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; HadoopHDFSMapReduceHiveFlumeSqoopZookeeperHBaseRedis 章节内容 上一节我们完成了&#xff1a; HBase …

组合数学+费用背包+刷表,G2 - Playlist for Polycarp (hard version)

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 G2 - Playlist for Polycarp (hard version) 二、解题报告 1、思路分析 一…

【flink】之如何快速搭建一个flink项目

1.通过命令快速生成一个flink项目 curl https://flink.apache.org/q/quickstart.sh | bash -s 1.19.1 生成文件目录&#xff1a; 其中pom文件包好我们所需要的基础flink相关依赖 2.测试 public class DataStreamJob {public static void main(String[] args) throws Except…

苍穹外卖(一)之环境搭建篇

Ngnix启动一闪而退 启动之前需要确保ngnix.exe的目录中没有中文字体&#xff0c;在conf目录下的nginx.conf文件查看ngnix的端口号&#xff0c;一般默认为80&#xff0c;若80端口被占用就会出现闪退现象。我们可以通过logs/error.log查看错误信息&#xff0c;错误信息如下&…

百日筑基第二十四天-23种设计模式-结构型总汇

百日筑基第二十四天-23种设计模式-结构型总汇 前言 设计模式可以说是对于七大设计原则的实现。 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1a;单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式。结构型模式&#xff0c;共…

SAPUI5基础知识16 - 深入理解MVC架构

1. 背景 经过一系列的练习&#xff0c;相信大家对于SAPUI5的应用程序已经有了直观的认识&#xff0c;我们在练习中介绍了视图、控制器、模型的概念和用法。在本篇博客中&#xff0c;让我们回顾总结下这些知识点&#xff0c;更深入地理解SAPUI5的MVC架构。 首先&#xff0c;让…

react native 截图并保存到相册

首先需要三个包 react-native-view-shot &#xff08;截图&#xff0c;将图片保存到临时路径&#xff09;react-native-fs &#xff08;更改图片路径&#xff0c;从临时路径移出来&#xff09;react-native-camera-roll/camera-roll &#xff08;将图片保存到相册&#xff09;…

web前端 Vue 框架面试120题(三)

面试题 41 . 如何理解Vue中的模板编译原理? 参考回答&#xff1a; 关于Vue编译原理这块的整体逻辑主要分为三步&#xff1a;第一步将模版字符串转换成element ASTs(解析器) 第二步是对AST进行静态节点标记&#xff0c;主要用来做虚拟DOM的渲染优化(优化器) 第三步是使用elem…

【AMD/Xilinx】FPGA远程烧录调试工具安装及使用

问题描述 在学习工作中&#xff0c;本人遇到了连接FPGA的服务器电脑没有Vivado或Vivado版本较低&#xff0c;导致没办法查看ila的情况。在这种情况下一方面重新安装Vivado需要占用大量存储空间&#xff0c;另一方面使用远程桌面软件连接服务器电脑的画质较为模糊&#xff0c;影…

保姆级教程!!教你通过【Pycharm远程】连接服务器运行项目代码

小罗碎碎念 这篇文章主要解决一个问题——我有服务器&#xff0c;但是不知道怎么拿来写代码&#xff0c;跑深度学习项目。确实&#xff0c;玩深度学习的成本比较高&#xff0c;无论是前期的学习成本&#xff0c;还是你需要具备的硬件成本&#xff0c;都是拦路虎。小罗没有办法…

使用Web控制端和轻量级客户端构建的开放Web应用防火墙(OpenWAF)

目录 1. 简介2. 项目结构3. Web控制端3.1. 功能概述3.2. 审计&#xff08;攻击&#xff09;日志查看3.3. 多个WAF的集中监控和操作3.4. 使用socket进行封装3.5. 日志的高效存储和检索&#xff08;Redis&#xff09; 4. 轻量级客户端4.1. 功能概述4.2. 对Web程序的防护4.3. 网络…

网页制作技术的未来发展趋势是什么?

网页制作技术的未来发展趋势是什么&#xff1f; 李升伟 以下是网页制作技术未来可能的一些发展趋势&#xff1a; 1. AI 助力设计&#xff1a;人工智能将在网页设计中发挥更大的作用&#xff0c;例如利用 AI 生成图像、快速构建网页布局、润色文案、优化色彩和字体等&#x…

PYQT按键长按机制

长按按键不松开也会触发 keyReleaseEvent 事件&#xff0c;是由于操作系统的键盘事件处理机制。大多数操作系统在检测到键盘按键被长按时&#xff0c;会重复生成按键按下 (keyPressEvent) 和按键释放 (keyReleaseEvent) 事件。这种行为通常被称为“键盘自动重复”。 通过检测 …

纯css实现语音播报动画效果

先来看看效果图 黑色以下代码 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAAF6fiUAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAO8SURBVGiB1ZlPaBxVHMe/v7fbkmUZErRs9/1moUtYBBfxklZR8aYg5KAgvcT25MFDvUhPTaGnHoKIlOpJ0VsPUvAgKNjSS0EPAVGKya…

数据结构 - 队列(精简介绍)

文章目录 单端队列单端队列操作&#xff1a;Queue实现 双端队列双端队列操作&#xff1a;Deque实现 循环队列循环队列手动实现 优先级队列Q 不断取最大礼物并开方 单端队列 普通队列为单端队列&#xff0c;先进先出&#xff08;FIFO&#xff09; 只能从尾部插入&#xff0c;头…

【MySQL进阶篇】SQL优化

1、插入数据 insert优化 批量插入&#xff1a; insert into tb_user values(1,tom),(2,cat),(3,jerry); 如果插入数据过大&#xff0c;可以将业务分割为多条insert语句进行插入。 手动提交事务&#xff1a; start transaction; insert into tb_user values(1,tom),(2,cat),(3…

AI算不出9.11和9.9哪个大?六家大模型厂商总结了这些原因

大模型“答对”或“答错”其实是个概率问题。关于“9.11和9.9哪个大”&#xff0c;这样一道小学生难度的数学题难倒了一众海内外AI大模型。7月17日&#xff0c;第一财经报道了国内外“12个大模型8个都会答错”这道题的现象&#xff0c;大模型的数学能力引发讨论。 “从技术人员…

go exporter开发 第一篇

为什么go程序要采集指标? 通过采集指标,可以从外部观测到程序运行中的一些运行中的数据,比如协程数,web请求的接口等情况,从而进一步分析程序是否有不退出的协程,以及性能,是否存在内存泄漏,通过对接Prometheus,能够观测接口请求时间,访问量,访问成功和访问失败等 …

puzzle(0611)《组合+图论》追捕问题

目录 一&#xff0c;追及问题 1&#xff0c;警察和小偷 2&#xff0c;旋转的4个硬币 3&#xff0c;抓狐狸 二&#xff0c;围堵问题 三&#xff0c;追及围堵 一&#xff0c;追及问题 1&#xff0c;警察和小偷 如下图&#xff0c;警察先走&#xff0c;警察和小偷轮流一人…

【LLM】基于ColossalAI-0.3.6对llama2-7B-Chat做全参数微调

文章目录 环境准备工作下载llama2-7B下载ColossalAI数据集准备准备原始数据集数据集处理开始训练准备训练脚本运行脚本推理验证加载模型推理环境 操作系统: ubuntu22.04机器规格: CPU:96c;内存:736 GiB;GPU:8 * NVIDIA V100 (32GB)软件信息: Python 3.11.5;ColossalA…