LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的数据予以淘汰。
很多缓存中采用这种算法。
LinkedHashMap
会对数据使用来排序,这样服务LRU算法的最少使用的思想了
public class LRUCacheDemo<k, v> extends LinkedHashMap<k, v> { private int capacity; public LRUCacheDemo(int capacity) { // assesOrder 我们就清楚了当accessOrder设置为false时,会按照插入顺序进行排序,当accessOrder为true是,会按照访问顺序 super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<k, v> eldest) { return super.size() > capacity; } public static void main(String[] args) { LRUCacheDemo lruCacheDemo = new LRUCacheDemo<>(3); lruCacheDemo.put(1, "a"); lruCacheDemo.put(2, "b"); lruCacheDemo.put(3, "c"); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.keySet()); lruCacheDemo.put(4, "d"); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.keySet()); lruCacheDemo.get(3); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.keySet()); lruCacheDemo.put(5, "e"); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.keySet()); } }
我们定义一个cacheDemo类继承这个LinkedHashMap
,Cache类构造器,提供capacity 参数,作为LRU清理缓存的边界。
复写removeEldestEntry
方法,当我们缓存的容量达到设定的边界的时候,清理最少使用的缓存。
特别说明:调用父类构造的时候,有一个参数assesOrder
这个参数的作用就是,当accessOrder设置为false时,会按照插入顺序进行排序,当accessOrder为true是,会按照访问顺序,这里我们最为缓存就设置为true。
测试结果:
lruCacheDemo.keySet() = [1, 2, 3] lruCacheDemo.keySet() = [2, 3, 4] lruCacheDemo.keySet() = [2, 4, 3] lruCacheDemo.keySet() = [4, 3, 5]
我们插入a,b,三个值,插入第四个的时候,第一个清理了,当我们访问3的时候,3排队到最后了,2是最少访问的。继续插入5,2就退出了。
这个模式是借鉴了,AQS同步队列的,通过hash保证访问速度,链表中的头尾节点来记录最近访问,和最少访问的数据。
package com.leven.demoplus.javase.algorithm; import java.util.HashMap; /** * 基于lru算法的缓存设计:最近最少使用 */ public class LRUCacheDemo2 { // 缓存容量 private int capacity; // hashmap用于存数据 private HashMap<Integer,Node<Integer,Integer>> map; // 链表数据结构维护访问次数:1:最近访问的放在头节点 2:缓存容量达到限制的时候,移除链表的尾节 private DoubleLinkList doubleLinkList; public LRUCacheDemo2(int capacity) { this.capacity = capacity; map = new HashMap(); doubleLinkList = new DoubleLinkList(); } public int get(int key){ if (!map.containsKey(key)){ return -1; } Node<Integer, Integer> node = map.get(key); // 访问的数据node,移动到链表的头head doubleLinkList.removeNode(node); doubleLinkList.addHead(node); return node.value; } public void put(Integer key, Integer value){ if (map.containsKey(key)){ Node<Integer, Integer> node = map.get(key); node.value = value; // 新加入数据加入头节点 doubleLinkList.removeNode(node); doubleLinkList.addHead(node); }else { // 缓存空间满了 if (map.size() >= capacity){ // 容量达到限制,淘汰尾节点数据 Node lastNode = doubleLinkList.getLastNode(); doubleLinkList.removeNode(lastNode); map.remove(lastNode.key); } Node<Integer, Integer> newNode = new Node<>(key, value); map.put(key,newNode); doubleLinkList.addHead(newNode); } } class DoubleLinkList<k,v>{ Node<k,v> head; Node<k,v> tail; public DoubleLinkList() { head = new Node<>(); tail = new Node<>(); head.next = tail; tail.pre = head; } // 添加到头 public void addHead(Node<k,v> node){ node.next = head.next; node.pre = head; head.next.pre = node; head.next = node; } public void removeNode(Node<k,v> node){ node.next.pre = node.pre; node.pre.next = node.next; node.pre = null; node.next = null; } public Node<k,v> getLastNode(){ return tail.pre; } } class Node<k,v>{ k key; v value; Node<k,v> pre; Node<k,v> next; public Node() { this.pre = this.next = null; } public Node(k key, v value) { this.key = key; this.value = value; } } public static void main(String[] args) { LRUCacheDemo2 lruCacheDemo = new LRUCacheDemo2(3); lruCacheDemo.put(1,1); lruCacheDemo.put(2, 2); lruCacheDemo.put(3, 3); // 1:预期:容量达到限制,淘汰尾节点1,打印:[1, 2, 3] System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.map.keySet()); // 2:预期:容量达到限制,淘汰尾节点1,打印:[2, 3, 4] lruCacheDemo.put(4, 4); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.map.keySet()); // 3:预期:访问4的数据,最小访问,加入头节点,打印:[2, 3, 4] lruCacheDemo.get(2); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.map.keySet()); // 4:预期:加入5,达到限制,淘汰最久访问的缓存3(如果不执行第三步的话淘汰的应该是2),打印:[2, 4, 5] lruCacheDemo.put(5, 5); System.out.println("lruCacheDemo.keySet() = " + lruCacheDemo.map.keySet()); } }
that is all!!