LFU算法
最近最少次数访问,缓存满的时候,优先淘汰掉最少访问的数据,若最少访问次数的数据有多个,淘汰掉访问时间与现在相隔最远的数据。
实现
堆 + 版本号
由于涉及到构建堆,所有操作的时间复杂度均为O(logN)
public class LFU{
class Node{
int key;
int val;
int count;
int version;
Node(int key, int val, int count){
this.key = key;
this.val = val;
this.count = count;
}
}
int capacity;
int count;
int version;
public HashMap<Integer, Node> map;
PriorityQueue<Node> heap;
LFU(int capacity){
this.capacity = capacity;
this.count = 0;
this.version = 0;
map = new HashMap<>();
heap = new PriorityQueue<Node>((node1, node2) ->{
if (node1.count == node2.count)
return node1.version - node2.version;
return node1.count - node2.count;
});
}
public void set(int key, int val){
Node node = map.get(key);
System.out.print("set key: " + key);
if (node == null){
if (count == capacity)
removeLFU();
node = new Node(key, val, 0);
map.put(key, node);
count++;
node.count++;;
node.version = version++;
heap.add(node);
} else {
node.val = val;
version++;
node.count++;;
node.version = version++;
heap.remove(node);
heap.add(node);
}
System.out.println();
}
public int get(int key){
// System.out.println("get key: " + key);
Node node = map.get(key);
version++;
if (node == null)
return -1;
else {
node.count++;
node.version = version;
heap.remove(node);
heap.add(node);
return node.val;
}
}
private void removeLFU() {
// System.out.print("remove");
Node node = heap.poll();
if (node != null) {
// System.out.print("remove key: " + node.key + " count: " + node.count);
map.remove(node.key);
count--;
}
}
}
双哈希表+LinkedList
set get 操作时间复杂度最优能到O(1),但是由于LinkedList的删除操作时间复杂度大于O(1),所以该版本的LFU缓存的操作的时间复杂度仍然大于O(1)。
/**
* @author 49367
* @date 2021/4/30 15:21
*/
public class LFU {
class Node {
int key;
int val;
// 访问次数
int freq;
public Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
}
}
HashMap<Integer, Node> nodeMap;
HashMap<Integer, LinkedList<Node>> freqTable;
// 容量,目前结点数量,最小访问次数
int capacity, count, min;
LFU(int capacity){
nodeMap = new HashMap<>();
freqTable = new HashMap<>();
this.capacity = capacity;
this.count = 0;
this.min = 1;
}
public void set(int key, int val){
if (capacity == 0)
return;
Node node = nodeMap.get(key);
if (node == null){
// 缓存已满,执行lfu策略
if (count == capacity)
removeLRU();
node = new Node(key, val, 1);
// 将结点加到nodeMap和freqMap中
LinkedList<Node> list = freqTable.get(1);
if (list == null)
list = new LinkedList<>();
list.addLast(node);
// list可能是新建的,所以要put到table里
freqTable.put(1, list);
nodeMap.put(key, node);
// 访问次数为1的链表不为空了,更新min为1
this.min = 1;
this.count++;
} else {
node.val = val;
viewNode(node);
}
}
public int get(int key){
Node node = nodeMap.get(key);
if (node == null)
return -1;
// node被访问的相关操作
viewNode(node);
return node.val;
}
/**
* node被访问(set或get操作)
* @param node
*/
public void viewNode(Node node){
/** node的访问次数加一,在freqTable中的位置应该改变 */
// 首先从原来的链表中删掉
LinkedList<Node> list = freqTable.get(node.freq);
list.remove(node);
// 如果min等于node原来的访问次数,且对应的链表已为空, min++
if (node.freq == this.min && list.isEmpty())
this.min++;
node.freq++;
// 放到新的访问次数的链表中去
int newFreq = node.freq;
LinkedList<Node> newList = freqTable.get(newFreq);
if (newList == null)
newList = new LinkedList<>();
newList.addLast(node);
freqTable.put(newFreq, newList);
}
/**
* 根据lfu策略删除结点
*/
private void removeLRU() {
LinkedList<Node> list = freqTable.get(min);
// min对应的链表头结点即为最近调用次数最少的node
if (list != null){
Node rmNode = list.removeFirst();
nodeMap.remove(rmNode.key);
}
this.count--;
}
}
双哈希表+自定义双向链表 (时间复杂度最低)
由于自定链表的删除操作的时间复杂度也是O(1),所以能够保证LFU缓存的set和get操作的时间复杂度都为O(1)。
public class LFU {
class Node {
int key;
int val;
// 访问次数
int freq;
Node pre, next;
public Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
this.pre = null;
this.next = null;
}
public Node(){
this.pre = null;
this.next = null;
}
}
class DoubleLinkedList{
/**
* 头结点和尾结点不为虚结点,节省空间
*/
Node head;
Node tail;
public DoubleLinkedList(){
this.head = null;
}
public void addFirst(Node node){
Node oldHead = head;
head = node;
// 原头结点成为新头结点的下一个结点
if (oldHead != null){
head.next = oldHead;
oldHead.pre = head;
}
// 如果尾结点为空,尾结点指向该node
if (tail == null)
tail = node;
}
public void addLast(Node node){
Node oldTail = tail;
tail = node;
// 原头结点成为新头结点的下一个结点
if (oldTail != null){
tail.pre = oldTail;
oldTail.next = tail;
}
// 如果头结点为空,则指向该node
if (head == null)
head = node;
}
public void remove(Node node){
if (node.pre != null)
node.pre.next = node.next;
if (node.next != null)
node.next.pre = node.pre;
if (node == head)
head = head.next;
if (node == tail)
tail = tail.pre;
// 必须将node与链表彻底断开连接
node.pre = null;
node.next = null;
}
public boolean isEmpty(){
return head == null;
}
public Node removeFirst() {
if (head == null)
return null;
Node res = head;
// 将该结点从链表中删去
remove(res);
return res;
}
}
HashMap<Integer, Node> nodeMap;
HashMap<Integer, DoubleLinkedList> freqTable;
// 容量,目前结点数量,最小访问次数
int capacity, count, min;
LFU(int capacity){
nodeMap = new HashMap<>();
freqTable = new HashMap<>();
this.capacity = capacity;
this.count = 0;
this.min = 1;
}
public void set(int key, int val){
if (capacity == 0)
return;
Node node = nodeMap.get(key);
if (node == null){
// 缓存已满,执行lfu策略
if (count == capacity)
removeLRU();
node = new Node(key, val, 1);
// 将结点加到nodeMap和freqMap中
DoubleLinkedList list = freqTable.get(1);
if (list == null)
list = new DoubleLinkedList();
list.addLast(node);
// list可能是新建的,所以要put到table里
freqTable.put(1, list);
nodeMap.put(key, node);
// 访问次数为1的链表不为空了,更新min为1
this.min = 1;
this.count++;
} else {
node.val = val;
viewNode(node);
}
}
public int get(int key){
Node node = nodeMap.get(key);
if (node == null)
return -1;
// node被访问的相关操作
viewNode(node);
return node.val;
}
/**
* node被访问(set或get操作)
* @param node
*/
public void viewNode(Node node){
/** node的访问次数加一,在freqTable中的位置应该改变 */
// 首先从原来的链表中删掉
DoubleLinkedList list = freqTable.get(node.freq);
list.remove(node);
// 如果min等于node原来的访问次数,且对应的链表已为空, min++
if (node.freq == this.min && list.isEmpty())
this.min++;
node.freq++;
// 放到新的访问次数的链表中去
int newFreq = node.freq;
DoubleLinkedList newList = freqTable.get(newFreq);
if (newList == null)
newList = new DoubleLinkedList();
newList.addLast(node);
freqTable.put(newFreq, newList);
}
/**
* 根据lfu策略删除结点
*/
private void removeLRU() {
DoubleLinkedList list = freqTable.get(min);
// min对应的链表头结点即为最近调用次数最少的node
if (list != null){
Node rmNode = list.removeFirst();
nodeMap.remove(rmNode.key);
}
this.count--;
}
}