什么是LRU?LRU是什么?

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

当你看到这篇文章我就当你有了一定的基础,看过其他LRU的介绍博客了

网友:想了半天终于想明白了为啥要用hashmap和双向链表,

<1>首先我想的是用队列不行吗?

不行,队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端。

<2> 就用单链表不行吗?

单链表能实现新来的放头部,最久不用的在尾部删除,但删除的时候需要遍历到尾部,因为单链表只有头指针。在用到已经用到过的数据时,还要遍历整合链表,来确定是否用过,然后再遍历到响应位置来剔除的节点,并重新放在头部。这效率可想而知。

这时hashmap的作用就出来了, 它可以在单位的时间判断value的值是否存在,key直接存储节点对象,能直接定位删除对应的节点(将比节点的父节点指向子节点)。要通过一个节点直接获得父节点的话,通过单链表是不行的。
这时双向链表的作用也提现出来了。能直接定位到父节点。 这效率就很高了。而且由于双向链表有尾指针,所以剔除最后的尾节点也十分方便,快捷。

下面的代码就是双向链表和HashMap实现的:

  1. package com.company.test01;
  2. import java.util.Map;
  3. import java.util.concurrent.ConcurrentHashMap;
  4. /**
  5. * @ClassName : LRUCache //类名
  6. * @Description : LRU //描述
  7. * @Author : Mr.Duan //作者
  8. * @Date: 2020-08-13 08:57 //时间
  9. */
  10. public class LRUCache<V> {
  11. private int capacity=1024;//容量
  12. private final Map<String, ListNode<String, V>> table = new ConcurrentHashMap<>();//记录Node
  13. private final ListNode<String, V> head;
  14. private final ListNode<String, V> tail;
  15. public LRUCache(int capacity) {
  16. this();
  17. this.capacity = capacity;
  18. }
  19. public LRUCache() {
  20. head = new ListNode<>();
  21. tail = new ListNode<>();
  22. head.next = tail;
  23. head.prev = null;
  24. tail.prev = head;
  25. tail.next = null;
  26. }
  27. public void get(String key) {
  28. ListNode<String, V> node = table.get(key);
  29. //如果Node不在表中,代表缓存中并没有
  30. if (node == null) {
  31. return;
  32. }
  33. //如果存在,则需要移动Node节点到表头
  34. //截断链表,node.prev -> node -> node.next ====> node.prev -> node.next
  35. // node.prev <- node <- node.next ====> node.prev <- node.next
  36. node.prev.next = node.next;
  37. node.next.prev = node.prev;
  38. //移动节点到表头
  39. node.next = head.next;
  40. head.next.prev = node;
  41. node.prev = head;
  42. head.next = node;
  43. //存在缓存表
  44. table.put(key, node);
  45. }
  46. @Override
  47. public String toString() {
  48. return "{" +
  49. "capacity=" + capacity +
  50. ", table=" + table +
  51. '}'+"\n";
  52. }
  53. public void put(String key, V value) {
  54. ListNode<String, V> node = table.get(key);
  55. //如果Node不在表中,代表缓存中并没有
  56. if (node == null) {
  57. if (table.size() == capacity) {
  58. //超过容量了 ,首先移除尾部的节点
  59. table.remove(tail.prev.key);//移除尾节点的上一个节点
  60. tail.prev.prev.next = tail;//尾部的上上个节点的下个节点设置为尾节点
  61. tail.prev = tail.prev.prev;//尾部的上个节点设置为尾部的上上个节点
  62. }
  63. node = new ListNode<>();
  64. node.key = key;
  65. node.value = value;
  66. table.put(key, node);
  67. }
  68. //如果存在,则需要移动Node节点到表头
  69. node.next = head.next;
  70. head.next.prev = node;
  71. node.prev = head;
  72. head.next = node;
  73. }
  74. /**
  75. * 双向链表内部类
  76. */
  77. public static class ListNode<K, V> {
  78. private K key;
  79. private V value;
  80. ListNode<K, V> prev;
  81. ListNode<K, V> next;
  82. public ListNode(K key, V value) {
  83. this.key = key;
  84. this.value = value;
  85. }
  86. public ListNode() {
  87. }
  88. @Override
  89. public String toString() {
  90. return "{" +
  91. "key=" + key +
  92. ", value=" + value +
  93. '}'+"\n";
  94. }
  95. }
  96. public static void main(String[] args) {
  97. LRUCache<ListNode> cache = new LRUCache<>(4);
  98. ListNode<String, Integer> node1 = new ListNode<>("key1", 1);
  99. ListNode<String, Integer> node2 = new ListNode<>("key2", 2);
  100. ListNode<String, Integer> node3 = new ListNode<>("key3", 3);
  101. ListNode<String, Integer> node4 = new ListNode<>("key4", 4);
  102. ListNode<String, Integer> node5 = new ListNode<>("key5", 5);
  103. cache.put("key1", node1);
  104. System.out.println("插入了:"+node1);
  105. System.out.println(cache);
  106. cache.put("key2", node2);
  107. System.out.println("插入了:"+node2);
  108. System.out.println(cache);
  109. cache.put("key3", node3);
  110. System.out.println("插入了:"+node3);
  111. System.out.println(cache);
  112. cache.put("key4", node4);
  113. System.out.println("插入了:"+node4);
  114. System.out.println(cache);
  115. cache.get("key2");
  116. System.out.println("获取了:"+node2);
  117. System.out.println(cache);
  118. cache.put("key5", node5);
  119. System.out.println("插入了:"+node5);
  120. System.out.println(cache);
  121. cache.get("key2");
  122. System.out.println("获取了:"+node2);
  123. System.out.println(cache);
  124. }
  125. }

(注意:上面代码为了方便观察表的变化重写了类中的toString()方法和main中的打印语句,正常状况下是没有的)代码运行结果如下:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=52888:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test01.LRUCache
  2. 插入了:{key=key1, value=1}
  3. {capacity=4, table={key1={key=key1, value={key=key1, value=1}
  4. }
  5. }}
  6. 插入了:{key=key2, value=2}
  7. {capacity=4, table={key1={key=key1, value={key=key1, value=1}
  8. }
  9. , key2={key=key2, value={key=key2, value=2}
  10. }
  11. }}
  12. 插入了:{key=key3, value=3}
  13. {capacity=4, table={key1={key=key1, value={key=key1, value=1}
  14. }
  15. , key2={key=key2, value={key=key2, value=2}
  16. }
  17. , key3={key=key3, value={key=key3, value=3}
  18. }
  19. }}
  20. 插入了:{key=key4, value=4}
  21. {capacity=4, table={key1={key=key1, value={key=key1, value=1}
  22. }
  23. , key2={key=key2, value={key=key2, value=2}
  24. }
  25. , key3={key=key3, value={key=key3, value=3}
  26. }
  27. , key4={key=key4, value={key=key4, value=4}
  28. }
  29. }}
  30. 获取了:{key=key2, value=2}
  31. {capacity=4, table={key1={key=key1, value={key=key1, value=1}
  32. }
  33. , key2={key=key2, value={key=key2, value=2}
  34. }
  35. , key3={key=key3, value={key=key3, value=3}
  36. }
  37. , key4={key=key4, value={key=key4, value=4}
  38. }
  39. }}
  40. 插入了:{key=key5, value=5}
  41. {capacity=4, table={key2={key=key2, value={key=key2, value=2}
  42. }
  43. , key5={key=key5, value={key=key5, value=5}
  44. }
  45. , key3={key=key3, value={key=key3, value=3}
  46. }
  47. , key4={key=key4, value={key=key4, value=4}
  48. }
  49. }}
  50. 获取了:{key=key2, value=2}
  51. {capacity=4, table={key2={key=key2, value={key=key2, value=2}
  52. }
  53. , key5={key=key5, value={key=key5, value=5}
  54. }
  55. , key3={key=key3, value={key=key3, value=3}
  56. }
  57. , key4={key=key4, value={key=key4, value=4}
  58. }
  59. }}
  60. Process finished with exit code 0