某程序员大佬,巧妙利用LRU算法整理房间——“LRU收纳法”
作者:ze ran
原文地址:
https://zhuanlan.zhihu.com/p/68550375
来源:知乎
前言
偶然在知乎上刷到了一个关于收纳的思维实验,想了想就写一篇关于LRU算法的文章吧!
知乎:一个关于收纳的思维实验
从抽屉里翻出个PSP 2000,想开机看看,却找不到充电器了。经过两天的翻箱倒柜,我决定,收拾房间!
做事情,有两类人,一类是实践先行,直接上手,边做边学;另一类是理论先行,宏观把握,阅读前人的思想,从战略的角度思考问题的本质,先拖着,受不了再做。
老婆是前者,我是后者。
我让老婆先 hold 住,不要动手,容我思考一下策略。
面对着摆的像圣诞树一样的 billy,我陷入了沉思:
假如有十二个盒子,编号一到十二,每个盒子代表一个月份。
用过的东西,不必归位,只要丢到当月的盒子里。比如,今天用过的东西,就丢到6号盒子里。
找东西,先从当月的盒子里找。没有,就找上个月的盒子,比如,现在是六月,查找盒子的顺序就6,5,4,3...
一年后,还在6号盒子里的东西,就是一年都没用过的,可以考虑扔了或回收。
好处有三:
1,房间的整洁度始终一致,维护难度小。传统收纳方法,人始终处在“乱--治--乱--治“的轮回中不能自拔,与熵增做着无谓的斗争。不必收纳,才是最容易的收纳。
2,解决的收纳后,常用的东西不好拿的问题。这个收纳算法中,最常用的东西,总是在当月的盒子里。不存在每天用的和每年用的东西混一起,反而找不到的烦恼。
3,自动筛选出无用的东西。收纳的时候,不必痛苦的考虑,扔不扔,有没有用。还在一年前的盒子里的东西,就是事实上不需要的东西。
缺点是:
要有十二个盒子。
老婆是不会同意再买十二个大盒子的,所以只能是个思维实验。有新搬家的同学,可以试试这个方法,可能很好玩。
这就叫 LRU 收纳法!
到这来知乎作者的文章就结束了,你以为这就结束了?不!
我们来谈谈缓存淘汰算法——LRU算法
1.概念分析
LRU(Least Recently Used),即最近最少使用,怎么理解这个概念呢?我一开始见到这个概念的时候,以为“最近”,"最少"都是修饰使用的(从中文解释中可以看出),不过这种理解是错误的。最近是修饰最少的,故应该理解为:最近这段时间最少访问的,最少使用。
这样理解是不是更清晰一些呢?这样我们得出两个结论
1、LRU这种算法是会将近期最少使用的数据淘汰掉。
2、LRU淘汰算法似乎是将最近次数上使用最少的数据淘汰。
其实不然,或者说理解的不确切,更准确地说:LRU算法是将近期最不会访问的数据淘汰掉(请注意1和2的不同,1处注重了次数上的比较,2处却没有这层意义)。
它的核心思想是"如果数据最近被访问过,那么将来被访问的概率也很高"。反过来说:"如果数据最近这段时间一直都没有访问,那么将来被访问的概率也会很低"。
我知道这两句都是伪命题,就好像说一个人最近一直倒霉,那么他一辈子都会倒霉。不过LRU就是基于这种思想来的,如果一个指导思想本身就有很多问题,那么在指导现实行为时就更加荒唐了(似乎有点形而上学的意味了...)。
因此,我们在这里可以说:LRU是荒唐的,是简单粗暴的,是片面的。打住,似乎变成了LRU的批斗会了。
那么LRU就一无是处了?
不是的,LRU算法的优点在于简单,而且也可以解决一些实际问题。只不过没那么精确而已,很多时候LRU算法也会有不少冤假错案。本来不该剔除的数据就白白的牺牲掉了,但是我们还是要正视LRU的优点。
下面就讲解LRU的算法实现。
2.原理
我画了一个LRU淘汰算法的过程图:
下面简单讲解一下LRU实现原理(需要在这里说明一下:LRU一般采用链表方式实现,便于快速移动数据位置,虽然图中使用似乎是数组方式):
一开始缓存池是空的,缓存池中插入数据时不用担心容量不足的事情。因此这个过程就是类似队列的FIFO(但不止这些)。
在第5步将E插入到缓存池中后,缓存池已经满了(当然实际应用中不会让它到达缓存池的尺寸,一般到70%左右就要考虑淘汰机制了)。
当第6步将E插入缓存池的时候,发现缓存池已经满了,LRU会将最早加入到缓存池的数据淘汰掉(A,实在不好意思啊);
第7步,从缓存池中访问C,C被访问,从时间点上是最近最近访问的,将C移动到链表的头部(C侥幸暂时远离被淘汰的边缘);
第8步,将G插入缓存池中,G处于链表头部(B不幸被淘汰)。
大致的过程就是这样,关于淘汰机制只是后面的三步中会用到。画出前面六步的过程只是说明LRU插入元素的方式。在这个图中,我想大家应该可以明白为什么使用链表,而不使用数组(链表的插入和删除的时间复杂度都是O(1))。
3.优劣分析
命中率
命中率较高,不过偶发性的情况对LRU的命中影响很大,同时也会引入很多数据污染(比如很长时间只访问一次的数据,在后期的文章中会涉及到这一话题,会有改进的方案)。
复杂度
实现起来较为简单。
存储成本
几乎没有空间上浪费。
缺陷
仅仅从最近使用时间上考虑淘汰算法,没有考虑缓存单元的使用频率,可能会淘汰一些仍有价值的单元。
4.实现
暂时略,以后会采用伪代码和java语言的方式做简单的实现。
最后,如有哪里不正确的地方,请多多指教!