参考自这篇文章:LFU五种实现方式,从简单到复杂,实现了其中第五种方法。
数据结构如下:
1. DoubleLinkedList
每个节点代表不同的频次,按照从高到低的顺序。每个节点内部又维护了一个Node链表,用来存储同一频次下不同时间访问的缓存,按照时间顺序,从前到后,最前的是最近被访问的缓存。
//存储不同的频率,频率从高到低 public static class DoubleLinkedList { //前向节点 public DoubleLinkedList pre; //后向节点 public DoubleLinkedList next; //当前节点的访问评率 public int freq; //内部节点头节点,方便新增时O(1)复杂度插入 public Node head; //内部节点尾节点,方便删除旧节点时O(1)复杂度删除 public Node tail; //记录Node的大小 public int size; DoubleLinkedList(int freq) { head = new Node(freq); tail = new Node(freq); head.next = tail; tail.pre = head; this.freq = freq; size = 0; } //内部元素,是一个双向链表,存储同一频率下不同的节点,先后顺序就是访问顺序 public static class Node { public Node pre; public Node next; public String key; public String value; //找前一个外层节点 public int freq; //记录一下,freqInc方法会用 public DoubleLinkedList doubleLinkedList; Node(String key, String values) { this.key = key; this.value = values; freq = 1; } Node(int freq) { this.freq = freq; } } public void deleteNode(Node node) { node.pre.next = node.next; node.next.pre = node.pre; size--; } //删除尾节点之前的一个节点 public Node deleteLastNode() { Node node = tail.pre; tail.pre.pre.next = tail; tail.pre = tail.pre.pre; size--; return node; } //新增节点放在最前 public void addNode(Node node) { head.next.pre = node; node.next = head.next; head.next = node; node.pre = head; node.doubleLinkedList = this; size++; } }
2. 实现代码
public class LFUCache { //初始容量 private int cap; //缓存数,可以用map.size()替换 private int size; //头节点 private DoubleLinkedList head; //尾节点 private DoubleLinkedList tail; //O(1)拿到缓存 private Map<String/*键*/, DoubleLinkedList.Node/*缓存*/> map; LFUCache(int cap) { this.cap = cap; head = new DoubleLinkedList(-1); tail = new DoubleLinkedList(-1); head.next = tail; tail.pre = head; map = new HashMap<>(); } /** * 获取缓存 * * @param key 键 * @return 值 */ public String get(String key) { if (!map.containsKey(key)) { return null; } DoubleLinkedList.Node node = map.get(key); //更新频次 freqInc(node); return node.value; } /** * 新增缓存 * * @param key 键 * @param value 值 */ public void add(String key, String value) { if (!map.containsKey(key)) { //如果不存在 //是否超过容量 if (map.size() == cap) { //将最小频率,最久未访问的节点删除 DoubleLinkedList pre = tail.pre; DoubleLinkedList.Node node = pre.deleteLastNode(); //当外层节点的内层节点为空,删除这个节点 if (pre.size == 0) { deleteDoubleLinkedList(pre); } map.remove(node.key); } //没有溢出的情况 DoubleLinkedList.Node node = new DoubleLinkedList.Node(key, value); map.put(key, node); //找到最小的链表 DoubleLinkedList pre = tail.pre; if (pre.freq != node.freq) { //新增外层节点 DoubleLinkedList newLinkedList = new DoubleLinkedList(1); addDoubleLinkedList(pre, newLinkedList); newLinkedList.addNode(node); } else { pre.addNode(node); } } else { //已经存在,这时候更新值,并且修改节点的频次,跟get类似 DoubleLinkedList.Node node = map.get(key); node.value = value; //移动到新的频次 freqInc(node); } } //更新频次 public void freqInc(DoubleLinkedList.Node node) { //将节点移动到新的频率下 //1. 从原外层链表中移除 DoubleLinkedList oldLinkedList = node.doubleLinkedList; oldLinkedList.deleteNode(node); //1.1 删除完成后,如果没有元素,删除外层链表 if (oldLinkedList.size == 0) { deleteDoubleLinkedList(oldLinkedList); } //2. 频率加一 node.freq = node.freq + 1; //3. 移动到新外层链表 DoubleLinkedList pre = oldLinkedList.pre; if (pre.freq != node.freq) { //3.1 前一个节点频次不匹配,则新建一个外层节点 DoubleLinkedList newLinkedList = new DoubleLinkedList(node.freq); //3.2 插入到外层链表 addDoubleLinkedList(pre, newLinkedList); newLinkedList.addNode(node); } else { pre.addNode(node); } } private void deleteDoubleLinkedList(DoubleLinkedList doubleLinkedList) { doubleLinkedList.pre.next = doubleLinkedList.next; doubleLinkedList.next.pre = doubleLinkedList.pre; } //在某个节点之后新增一个节点 private void addDoubleLinkedList(DoubleLinkedList preLinkedList, DoubleLinkedList newLinkedList) { preLinkedList.next.pre = newLinkedList; newLinkedList.next = preLinkedList.next; preLinkedList.next = newLinkedList; newLinkedList.pre = preLinkedList; } //存储不同的频率,频率从高到低 public static class DoubleLinkedList { //... } public static void main(String[] args) { LFUCache cache = new LFUCache(2); cache.add("1", "苹果"); cache.add("2", "香蕉"); System.out.println(cache.get("1")); cache.add("3", "荔枝");//删除2,因为1访问频次更高 System.out.println(cache.get("2")); System.out.println(cache.get("3")); cache.add("4", "桃子");//删除1,因为1比3更久未被访问 System.out.println(cache.get("1")); System.out.println(cache.get("3")); System.out.println(cache.get("4")); } }