LRU 算法

LRU 算法

LRU算法原理及实现

前言

什么是LRU算法?

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

那么该数据结构就是当存储队列到达上限时,清除的是最久未被访问的节点,该节点一般认为是最可能无用的节点,保留下来的是最近都有使用过的节点,因此可以实现对"有用"数据的最大程度保留。

img

LRU算法应用场景?

LRU算法有许多的应用场景。

  1. Redis中使用LRU来进行淘汰
  2. 操作系统底层的内存管理,比如说页面置换算法中的LRU算法
  3. 业务处理,比如说做一个用户最近10个浏览记录,那么就可以使用LRU算法来维护一个大小为10的LRU队列

原理

LRU算法需要实现如下特性

  1. 实现get/put方法(都为O(1)的时间复杂度)
  2. 每次get时需要将访问的节点提前至队首
  3. 每次put需要判断队列是否已满,满了则将最后的节点删除,并且将该节点放至队首,不满则直接放队首

基于上述特性需要实现如下数据结构

  1. 首先需要实现队列,如果使用单向链表,当我们需要使用删除操作时,需要获得前置节点的指针,单向链表则不能做到直接获取。因此使用双向链表。
  2. 又我们需要get方法达到O(1)的时间复杂度,因此需要一个Hashmap,可以根据key定位到我们双向链表的Node节点。
  3. 由于我们HashMap中有key,所以我们可不可以Node中只存value,其实是不可以的,在队满时需要通过删除链表最后节点的 key 来反向找到 hash 表中应该覆盖的 key。

因此我们实现了如下数据结构

img

缓存淘汰过程如下

带你手写LRU算法

实现优化考虑

上述的LRU容器还是一个根本不能投入生产使用的玩具级实现,可以在进一步进行优化。

值的类型

上述的实现我们都是默认value是int,而且是正整数的int,然而生产中,不应该使用固定的value值。Java中应该使用泛型,GoLang中可以使用interface。

最大容量

上述我们的容器最大容量的单位是键值对的个数,这是不太合理的,因为实际中我们应该限制的是缓存占用大小,因此可以将最大限制改成byte为单位,而且需要对淘汰算法进行优化,这时候我们可能超出容量后,需要淘汰的不止是一个缓存,可以是多个,直到当前已用内存小于最大内存。

并发安全

上述我们写的只能在单线程下使用,没有考虑到并发问题,那么其实只需要对每次链表和队列的写查进行相应的加锁即可。Java可以使用synchronized关键字也可以使用ReentrantLock/ReentrantReadWriteLock来对其加锁。GoLang可以使用标准库的sync.Mutex来加锁。

其他

我们这次写的是每次都更新到首位的LRU,称为lru-1,也有lru-k的方式,这个需要根据情况进行优化。

LRU-K(Least Recently Used K)是一种基于LRU算法的变种,它在维护缓存中的数据访问顺序时,不仅考虑最近一次访问,还考虑了最近K次的访问情况。

具体来说,LRU-K算法会在缓存中记录每个数据最近K次被访问的位置,而不仅仅是最近一次。当需要淘汰数据时,LRU-K会选择最久未被访问的那些数据,也就是在过去K次访问中没有被访问的数据。这样做的目的是更好地适应特定访问模式,对于某些场景可以提供更好的性能。

LRU-K中的K可以根据实际应用情况来调整。较小的K值会更关注短期内的访问模式,而较大的K值则更加平滑地适应长期访问模式。选择合适的K值需要结合实际数据访问模式和性能需求进行权衡。

总之,LRU-K是一种对传统的LRU算法的扩展和改进,通过考虑多次访问的历史记录,可以更精确地判断哪些数据是最久未被使用的,从而在某些情况下提供更好的缓存性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值