- LRU (Least Recently Used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。
- LFU (Least Frequently Used) 最近最少次数使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。
LRU缓存
像浏览器的缓存策略、memcached的缓存策略都是使用LRU这个算法,LRU算法会将近期最不会访问的数据淘汰掉。LRU如此流行的原因是实现比较简单,而且对于实际问题也很实用,良好的运行时性能,命中率较高。
实现
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
- 当链表满的时候,将链表尾部的数据丢弃
API
set(key,value)
如果key在字典中存在,则先重置对应的value值,然后获取对应的节点curr,将curr节点从链表删除,并移动到链表的头部;
如果key在字典中不存在,则新建一个节点,并将节点放到链表的头部。当cache存满的时候,将链表最后一个节点删除即可。
get(key)
如果key在字典中存在,则把对应的节点放到链表头部,并返回对应的value值;
如果key在字典中不存在,则返回-1
优势
- 实现简单,使用单链表并对其进行操作即可实现
- 命中率高
LFU 缓存
相较于 LRU 缓存,LFU 缓存策略更符合热点数据的概念,即让多次被访问的数据缓存效果越好.但是LFU的实现相较于LRU要更为复杂,因为LFU算法除了需要根据访问时间排序外,还需要记录缓存的访问频率.目前市面上很多主流设计中都开始慢慢融入了 LFU 缓存,如 Redis 中的缓存逐出策略,还有Dubbo 中的缓存插件等.
实现
相较于 LRU 算法,LFU 算法实现上还需要记录缓存的访问次数,并按照缓存次数排序,如果访问次序相同的话,则按照访问时间排序。从这个角度来看的话 LFU 算法本身的实现复杂度就要比 LRU 复杂。除此之外,如果缓存的访问频次相同的话 ,LFU 算法就要退化称为 LRU 算法按照缓存的访问时间进行排序。因此又可以将 LFU 算法堪称 LRU的升级版。
按照访问频次进行排序的话用 PriorityQueue 队列实现效果是很好的,该队列内部维护了一个二叉堆,可以保证每次 poll 元素的时候,都可以根据我们的要求,去除当前所有元素的最大值或者最小值。只需要我们呢是心啊实体类中的 Conparable 接口就可以了。
- 判断新数据是否存在于 优先队列中,如果存在,则取出该元素并更新频次
- 当缓存满的时候,从优先队列中取出访问频次最低的 n 个元素删除
API
set(key,value)
如果key在字典中存在,则先重置对应的value值,然后更新key的访问时间与访问频次;
如果key在字典中不存在,则保存对应的 value ,然后更新key的访问时间与访问频次。
get(key)
如果key在字典中存在,则更新key的访问时间与访问频次,并返回对应的value值;
如果key在字典中不存在,则返回-1
优势
- 相较于 LRU 算法,基于频次的缓存汰换策略更加符合热点数据的思想,但实现起来较为复杂