Redis内存淘汰策略中会使用到 LFU 算法,以下是简单说明及Java代码实现。
一、前言
LFU(The Least Frequently Used)即最不经常使用算法。其原理是如果一个数据最近被访问次数不多,那么将来它被使用的可能也更低,因此在内存达到一定阈值时,将最不经常使用的数据淘汰的一种策略算法。
二、图解
TODO 图解说明后期有空再补充
三、LFU 算法 Java 实现
3.1 定义节点类
节点Node类中应包含 key(键)、value(值)、cnt(使用频次)和prev(前置节点)、next(后置节点)指针;
import lombok.Getter;
import lombok.Setter;
/**
* 节点Node
*/
@Getter
@Setter
public class Node {
/**
* 节点键key
*/
private int key;
/**
* 节点值value
*/
private int value;
/**
* 使用频次
*/
private int cnt;
/**
* 前置节点
*/
private Node prev;
/**
* 后置节点
*/
private Node next;
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
this.cnt = 1;
}
}
注意:代码中 @Getter、@Setter 使用的是 lombok 插件,如果没有安装直接删除这两个注解和对应 import 导入逻辑,再手工给所有属性添加 Getter/Setter 方法即可,后续代码也是不做另外说明;
3.2 双向链表实现
双向链表中定义 dummyHead(虚拟头节点)、dummyTail(虚拟尾节点)两个虚拟头尾节点,避免后续处理中针对头尾节点的特殊处理;再用 length 属性维护链表长度
即
真实头节点:dummyHead.next
真实尾节点:dummyTail.prev
双向链表中定义 add、delete、deleteHead 方法,实现对链表的新增、删除及删除头节点功能(因为我们新增插入是在链表尾部,所以如需淘汰从链表头开始);
import lombok.Getter;
import lombok.Setter;
/**
* 双向链表
* LFU相同使用频次实现使用双向链表存储,头尾节点(head、tail)使用两个默认的虚拟节点,这样避免使用时针对头尾节点的特殊处理
*/
@Getter
@Setter
public class DoubleLinkedList {
/**
* 虚拟头节点,实际头节点应为dummyHead.next
*/
private Node dummyHead;
/**
* 虚拟尾节点,实际尾节点应为dummyTail.prev
*/
private Node dummyTail;
/**
* 链表长度
*/
private int length;
public DoubleLinkedList() {
//默认虚拟头、尾节点初始化
this.dummyHead = new Node();
this.dummyTail = new Node();
this.dummyHead.setNext(this.dummyTail);
this.dummyTail.setPrev(this.dummyHead);
this.length = 0;
}
/**
* 添加节点
* 每次添加节点在链表末尾
* @param node
*/
public void add(Node node) {
node.setNext(this.dummyTail);
node.setPrev(this.dummyTail.getPrev());
this.dummyTail.getPrev().setNext(node);
this.dummyTail.setPrev(node);
this.length++;
}
/**
* 删除节点
* @param node
*/
public void delete(Node node) {
node.getPrev().setNext(node.getNext());
node.getNext().setPrev(node.getPrev());
node.setPrev(null);
node.setNext(null);
this