LRU与LFU
一 概念
LRU:最近最少使用淘汰算法(Least Recently Used)。LRU是淘汰最长时间没有被使用的数据
LFU:最不经常使用淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的数据
二 区别
LRU关键是看最后一次被使用到发生替换的时间长短,时间越长,就会被淘汰;而LFU关键是看一定时间段内被使用的频率(次数),使用频率越低,就会被淘汰
LRU算法适合:较大的文件比如游戏客户端(最近加载的地图文件)
LFU算法适合:较小的文件和教零碎的文件比如系统文件、应用程序文件
LRU消耗CPU资源较少,LFU消耗CPU资源较多
三实现
LRU实现:
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
- 当链表满的时候,将链表尾部的数据丢弃
LRU具备的操作:
- set(key, value):如果key在hashmap中存在,则先重置对应的value值,然后获取对应的节点cur,将cur节点从链表删除,并移动到链表的头部;若果key在hashmap不存在,则新建一个节点,并将节点放到链表的头部。当Cache存满的时候,将链表最后一个节点删除即可
- get(key):如果key在hashmap中存在,则把对应的节点放到链表头部,并返回对应的value值;如果不存在,则返回-1
完整基于 Java 的代码参考如下
class DLinkedNode { String key; int value; DLinkedNode pre; DLinkedNode post; }
LRU Cache
public class LRUCache { private Hashtable<Integer, DLinkedNode> cache = new Hashtable<Integer, DLinkedNode>(); private int count; private int capacity; private DLinkedNode head, tail; public LRUCache(int capacity) { this.count = 0; this.capacity = capacity; head = new DLinkedNode(); head.pre = null; tail = new DLinkedNode(); tail.post = null; head.post = tail; tail.pre = head; } public int get(String key) { DLinkedNode node = cache.get(key); if(node == null){ return -1; // should raise exception here. } // move the accessed node to the head; this.moveToHead(node); return node.value; } public void set(String key, int value) { DLinkedNode node = cache.get(key); if(node == null){ DLinkedNode newNode = new DLinkedNode(); newNode.key = key; newNode.value = value; this.cache.put(key, newNode); this.addNode(newNode); ++count; if(count > capacity){ // pop the tail DLinkedNode tail = this.popTail(); this.cache.remove(tail.key); --count; } }else{ // update the value. node.value = value; this.moveToHead(node); } } /** * Always add the new node right after head; */ private void addNode(DLinkedNode node){ node.pre = head; node.post = head.post; head.post.pre = node; head.post = node; } /** * Remove an existing node from the linked list. */ private void removeNode(DLinkedNode node){ DLinkedNode pre = node.pre; DLinkedNode post = node.post; pre.post = post; post.pre = pre; } /** * Move certain node in between to the head. */ private void moveToHead(DLinkedNode node){ this.removeNode(node); this.addNode(node); } // pop the current tail. private DLinkedNode popTail(){ DLinkedNode res = tail.pre; this.removeNode(res); return res; } }
LFU具备的操作:
- set(key, value):插入或修改缓存,如果key已存在,则将它对应的值改为value;如果key不存在,则插入键值对t(key, value),当缓存达到容量
capacity
时,则应该在插入新的键值对之前,删除使用频次最低的键值对。如果最低的键值对有多个,则删除其中最旧的那个
- get(key):会去缓存中查询键key,如果key存在,则返回key对应的value,否则返回 -1
完整基于 Java 的代码参考如下
class Node { int key;//键 int value;//值 int freq = 1;//频率 //构造方法 public Node() {} //构造方法 public Node(int key, int value) { this.key = key; this.value = value; } }
class LFUCache1 { Map<Integer, Node> cacheMap;//存储缓存的内容 Map<Integer,LinkedHashSet<Node>> freqMap; //存储每个频次对应的双向链表 int size; int capacity; int min;//存储最小频次 LFUCache1(int capacity) { cacheMap = new HashMap<Integer, p7.Node>(capacity); freqMap = new HashMap<Integer, LinkedHashSet<Node>>(); this.capacity = capacity; } public int get(int key) { Node node = cacheMap.get(key); if(node == null) { return -1; } freqInc(node);//增加一次频次 return node.value; } public void put(int key, int value) { if(capacity == 0) { return ; } Node node = cacheMap.get(key); if(node != null) { //已经存在了就替换掉,且增加一次频次 node.value = value; freqInc(node); }else { if(size == capacity) { //容量到达极限,移除 Node deadNode = removeNode(); cacheMap.remove(deadNode.key); size--; } Node newnodeNode = new Node(key,value); cacheMap.put(key, newnodeNode); //没存在过,说明要增加最开始频率为1 addNode(newnodeNode); size++; } } void freqInc(Node node) { //从原来对应的链表里面一处,并更新min; //获取这个节点的频率 int freq = node.freq; //获得当前节点频率找到对应频率的HashMap里面的LinkedHashSet LinkedHashSet<Node> set = freqMap.get(freq); //移除掉LinkedHashSet里面的这个元素 set.remove(node); if(freq == min && set.size() == 0) { min = freq +1; } //加入新freq对应的链表 node.freq++; LinkedHashSet<Node> newSet = freqMap.get(freq+1); if(newSet == null) { newSet = new LinkedHashSet<>(); freqMap.put(freq+1, newSet); } newSet.add(node); } void addNode(Node node) { LinkedHashSet<Node> set = freqMap.get(1); if( set == null) { set = new LinkedHashSet<p7.Node>(); freqMap.put(1, set); } set.add(node); min = 1; } Node removeNode() { LinkedHashSet<Node> set = freqMap.get(min); Node deadNode = set.iterator().next(); set.remove(deadNode); return deadNode; } }
不积跬步,无以至千里;不积小流,无以成江海