LRU是什么
按照英文的直接原义就是Least Recently Used,最近最久未使用法,它是按照一个非常著名的计算机操作系统基础理论得来的:最近使用的页面数据会在未来一段时期内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。基于这个思想,会存在一种缓存淘汰机制,每次从内存中找到最久未使用的数据然后置换出来,从而存入新的数据!它的主要衡量指标是使用的时间,附加指标是使用的次数。在计算机中大量使用了这个机制,它的合理性在于优先筛选热点数据,所谓热点数据,就是最近最多使用的数据!因为,利用LRU我们可以解决很多实际开发中的问题,并且很符合业务场景。
利用双向链表实现
双向链表有一个特点就是它的链表是双路的,我们定义好头节点和尾节点,然后利用先进先出(FIFO),最近被放入的数据会最早被获取。其中主要涉及到添加、访问、修改、删除操作。首先是添加,如果是新元素,直接放在链表头上面,其他的元素顺序往下移动;访问的话,在头节点的可以不用管,如果是在中间位置或者尾巴,就要将数据移动到头节点;修改操作也一样,修改原值之后,再将数据移动到头部;删除的话,直接删除,其他元素顺序移动;
基本的链表操作节点
public class Node { //键 Object key; //值 Object value; //上一个节点 Node pre; //下一个节点 Node next; public Node(Object key, Object value) { this.key = key; this.value = value; } }
具体的实现
1 import java.util.HashMap; 2 3 public class LRU<K, V> { 4 private int currentSize; //当前的大小 5 private int capcity; //总容量 6 private HashMap<K, Node> caches; //所有的node节点 7 private Node first; //头节点 8 private Node last; //尾节点 9 10 public LRU(int size) { 11 currentSize = 0; 12 this.capcity = size; 13 caches = new HashMap<K,Node>(size); 14 } 15 16 /** 17 * 放入元素 18 * 19 * @param key 20 * @param value 21 */ 22 public void put(K key, V value) { 23 Node node = caches.get(key); 24 //如果新元素 25 if (node == null) { 26 //如果超过元素容纳量 27 if (caches.size() >= capcity) { 28 //移除最后一个节点 29 caches.remove(last.key); 30 removeLast(); 31 } 32 //创建新节点 33 node = new Node(key, value); 34 } 35 //已经存在的元素覆盖旧值 36 node.value = value; 37 //把元素移动到首部 38 moveToHead(node); 39 caches.put(key, node); 40 } 41 42 /** 43 * 通过key获取元素 44 * 45 * @param key 46 * @return 47 */ 48 public Object get(K key) { 49 Node node = caches.get(key); 50 if (node == null) { 51 return null; 52 } 53 //把访问的节点移动到首部 54 moveToHead(node); 55 return node.value; 56 } 57 58 /** 59 * 根据key移除节点 60 * 61 * @param key 62 * @return 63 */ 64 public Object remove(K key) { 65 Node node = caches.get(key); 66 if (node != null) { 67 if (node.pre != null) { 68 node.pre.next = node.next; 69 } 70 if (node.next != null) { 71 node.next.pre = node.pre; 72 } 73 if (node == first) { 74 first = node.next; 75 } 76 if (node == last) { 77 last = node.pre; 78 } 79 } 80 return caches.remove(key); 81 } 82 83 /** 84 * 清除所有节点 85 */ 86 public void clear() { 87 first = null; 88 last = null; 89 caches.clear(); 90 } 91 92 /** 93 * 把当前节点移动到首部 94 * 95 * @param node 96 */ 97 private void moveToHead(Node node) { 98 if (first == node) { 99 return; 100 } 101 if (node.next != null) { 102 node.next.pre = node.pre; 103 } 104 if (node.pre != null) { 105 node.pre.next = node.next; 106 } 107 if (node == last) { 108 last = last.pre; 109 } 110 if (first == null || last == null) { 111 first = last = node; 112 return; 113 } 114 node.next = first; 115 first.pre = node; 116 first = node; 117 first.pre = null; 118 } 119 120 /** 121 * 移除最后一个节点 122 */ 123 private void removeLast() { 124 if (last != null) { 125 last = last.pre; 126 if (last == null) { 127 first = null; 128 } else { 129 last.next = null; 130 } 131 } 132 } 133 134 @Override 135 public String toString() { 136 StringBuilder sb = new StringBuilder(); 137 Node node = first; 138 while (node != null) { 139 sb.append(String.format("%s:%s ", node.key, node.value)); 140 node = node.next; 141 } 142 return sb.toString(); 143 } 144 145 146 public static void main(String[] args) { 147 LRU<Integer, String> lru = new LRU<Integer, String>(5); 148 lru.put(1, "a"); 149 lru.put(2, "b"); 150 lru.put(3, "c"); 151 lru.put(4, "d"); 152 lru.put(5, "e"); 153 System.out.println("原始链表为:" + lru.toString()); 154 155 lru.get(4); 156 System.out.println("获取key为4的元素之后的链表:" + lru.toString()); 157 158 lru.put(6, "f"); 159 System.out.println("新添加一个key为6之后的链表:" + lru.toString()); 160 161 lru.remove(3); 162 System.out.println("移除key=3的之后的链表:" + lru.toString()); 163 } 164 }