大小堆是笔者接触过的关于操作系统的算法,现在再添加一个LRU,也是在任务调度方面常常遇到的。最近也在 InnoDB 的缓冲池中遇到了优化的 LRU,当然 redis 中淘汰机制也有
LRU(Least Recently Used)基于一种假设——最近最少使用,也就是说最近使用得少的数据,在未来使用到的几率也不大,那么当资源不够用时,就可以选择移除那些最近最少使用的数据
我们将 Task(任务)使用双向链表连接起来,这样就明确了任务的时序关系(显示哪些最近使用与未使用)。而且当内存不够时就要将暂时不需要的数据移出,这就涉及到增删,增删使用双向链表的效率较高 O(1)。那么我们就可以创建一个 Node 节点来组成双向链表。其中key 为该任务的唯一标识,value 为具体的任务
class Node { K key; V value; Node pre; Node next; }
删除具体任务时需要将其查找出来然后才进行删除,双向链表的查找效率并不高O(N),说到查找我们可以利用哈希表,其查找效率为O(1)。结合哈希表和双向链表的形式其查找和增删的效率都为O(1)了,哈希表中的 key 就是任务的唯一标识,而 value 则为双向链表的 Node 节点
图里简化了 HashMap 的指向(有红黑树和拉链法的形式)
新任务:任务不在 HashMap 中,直接加入到链表尾部
旧任务:任务在 HashMap中,通过哈希表找到该任务,并其在链表中删除,然后插入到链表头部
淘汰:判断限制资源条件(长度或内存),移除 HashMap 中的 key,再移除链表末尾的节点
public class LRUCache<K, V> { private Long cacheSize; // 这里使用任务长度来限制资源 private Long currentSize; private HashMap<K, Node> hashMap; private Node first; // 链表头 private Node last; // 链表尾 /** * 双向链表的节点,存放具体内容 * key为唯一标志 */ class Node { K key; V value; Node pre; Node next; } /** * 限制任务长度 * * @param cacheSize */ public LRUCache(Long cacheSize) { this.currentSize = 0L; this.cacheSize = cacheSize; hashMap = new HashMap<K, Node>(); } /** * 放入hashmap方便快速查找、放入双向链表记录时序 * * @param key * @param value */ public void put(K key, V value) { Node node = hashMap.get(key); if (node == null) { if (size() >= cacheSize) { hashMap.remove(last.key); removeLast(); } node = new Node(); node.key = key; } node.value = value; moveToFirst(node); hashMap.put(key, node); if (size() < cacheSize) { currentSize++; } // 下面是顺手打印 StringBuilder sb = new StringBuilder(); Node pnode = first; while (pnode != null) { sb.append(String.format("%s:%s ", pnode.key, pnode.value)); pnode = pnode.next; } System.out.println(sb.toString()); // 上面是顺手打印 } /** * 舍弃最近最久未使用,即链尾 */ private void removeLast() { if (last != null) { last = last.pre; if (last == null) { first = null; } else { last.next = null; } } } /** * 最近使用,移动到表头 * * @param node */ private void moveToFirst(Node node) { if (first == node) { return; } if (node.next != null) { node.next.pre = node.pre; } if (node.pre != null) { node.pre.next = node.next; } if (node == last) { last = last.pre; } if (first == null || last == null) { first = last = node; return; } node.next = first; first.pre = node; first = node; first.pre = null; } /** * 获取某个任务,任务调度 * * @param key * @returnV */ public V get(K key) { Node node = hashMap.get(key); if (node == null) { return null; } moveToFirst(node); return node.value; } /** * 移除 * * @param key * @return */ public V remove(K key) { Node node = hashMap.get(key); if (node != null) { if (node.pre != null) { node.pre.next = node.next; } if (node.next != null) { node.next.pre = node.pre; } if (node == first) { first = node.next; } if (node == last) { last = node.pre; } } currentSize--; return hashMap.remove(key).value; } public boolean isEmpty() { return currentSize == 0; } public Long size() { return this.currentSize; } /** * 清空缓存 */ public void clear() { first = null; last = null; hashMap.clear(); currentSize = 0L; } public static void main(String[] args) throws Exception { LRUCache lruCache = new LRUCache<>(3L); lruCache.put(1, 1); lruCache.put(2, 2); lruCache.put(3, 3); lruCache.put(4, 4); lruCache.put(2, 2); } } // 1:1 // 2:2 1:1 // 3:3 2:2 1:1 // 4:4 3:3 2:2 // 2:2 4:4 3:3
笔者还在想着基于内存怎么限制,刚好学着 Elasticsearch ,所以了解到 lucene
public LRUCache(Long cacheSize) { Long JMVCache = 0L; if ((JMVCache = JVMUsebaleSize()) < cacheSize) { System.out.println("JVM可用大小为:" + JMVCache); System.out.println("当前设置为:" + cacheSize + ",但任然可用,超过内存即报错 OOM"); } this.cacheSize = cacheSize; hashMap = new HashMap<K, Node>(); } /** * 获取JVM可用内存大小,用于提示初始化 Cache 时可用内存不足 * * @return 字节 */ public Long JVMUsebaleSize() { Runtime runtime = Runtime.getRuntime(); long max = runtime.maxMemory(); long total = runtime.totalMemory(); long free = runtime.freeMemory(); return max - total + free; }
// getSizeof 获取的是对象本身内存大小,不包括内部属性指向的对象大小 // RamUsageEstimator.sizeOf() 包含内部属性指向,lucene-core4.0.0之前才有这个方法 public Long size() { return RamUsageEstimator.sizeOf(this); }
public String userPercent() { new DecimalFormat("0.00"); return (size() / 1.0 / this.cacheSize) * 100 + "%"; }
参考:
https://blog.csdn.net/wangxilong1991/article/details/70172302