当前位置:   article > 正文

LRU和LFU算法_lru算法和lfu

lru算法和lfu

redis的学习中,了解到了两种缓存清理算法,分别是LRU和LFU,在此针对这两种算法进行学习。

1.LRU算法

LRU(Least Recently Used)称为最近最少使用算法。基本的思想是:长期不被使用的数据,在未来被用到的几率也不大,因此当新的数据进来时,就可以优先将这些数据替换掉。

1.1 LRU算法中数据结构

哈希链表,哈希表我们知道是又多个k-v键值对组成的,而哈希链表就是将这些节点在连接起来,每个节点都有一个前驱节点和后继节点,就像双向链表中的节点一样,使得哈希表拥有了固定的排列顺序。基于这个有序性,就可以把k-v键值对按照使用时间的先后顺序进行排列。

在这里插入图片描述

 1.2 LRU算法思路

维护一个哈希表和双向链表,哈希表负责存储数据,双向链表维护顺序。当添加元素的时候,新的元素添加到双向链表的末尾,访问元素时,如果不存在则跳过,如果存在,则将该元素从双向链表中取出,然后在插入到链表的末尾。当需要进行清理的时候,优先清理链表头部的数据。(越靠近链表的头部,说明数据越少使用,因此可以将该数据优先删除---- 头尾无所谓,主要是有一头代表数据不经常使用

1.3 LRU代码实现

  1. public class LRUCache{
  2. private Map<Integer, ValueNode> map;
  3. private int capacity;
  4. private ValueNode head;
  5. private ValueNode tail;
  6. // LRU缓存构造器,初始化容量,map,头节点和尾节点
  7. public LRUCache(int capacity) {
  8. this.capacity = capacity;
  9. map = new HashMap<>();
  10. head = new ValueNode(-1, -1);
  11. tail = new ValueNode(-1, -1);
  12. head.right = tail;
  13. tail.left = head;
  14. }
  15. // get方法,如果key存在,取key的时候将节点从原来的位置删除,然后插入到最后
  16. public int get(int key) {
  17. if (map.containsKey(key)) {
  18. ValueNode valueNode = map.get(key);
  19. refreshNode(valueNode);
  20. return valueNode.value;
  21. }
  22. return -1;
  23. }
  24. // put方法,如果key存在,则替换原来的value,如果不存在,则put进去
  25. // 如果map中的数量已经超过了capacity,则删除最前面的节点,同时记得要在map中把这个节点删除!!!!
  26. // 最后刷新双向链表,将节点移至双向列表的最后面
  27. public void put(int key, int value) {
  28. ValueNode valueNode = null;
  29. if (map.containsKey(key)) {
  30. valueNode = map.get(key);
  31. valueNode.value = value;
  32. } else {
  33. valueNode = new ValueNode(key, value);
  34. if (map.size() >= capacity) {
  35. map.remove(head.right.key);
  36. deleteNode(head.right);
  37. }
  38. map.put(key, valueNode);
  39. }
  40. refreshNode(valueNode);
  41. }
  42. // 刷新双向列表,其实就是先吧当前节点在原链表中删除,然后在插入到链表的最后面
  43. private void refreshNode(ValueNode node) {
  44. deleteNode(node);
  45. ValueNode left = tail.left;
  46. node.right = tail;
  47. node.left = left;
  48. left.right = node;
  49. tail.left = node;
  50. }
  51. // 删除双向链表中的节点
  52. private void deleteNode(ValueNode node) {
  53. if (node.right != null) {
  54. ValueNode right = node.right;
  55. right.left = node.left;
  56. node.left.right = right;
  57. }
  58. }
  59. // 定义双向链表节点,包括键值,左右节点,构造器
  60. class ValueNode {
  61. // 定义键值
  62. int key;
  63. int value;
  64. // 定义左右节点
  65. ValueNode left;
  66. ValueNode right;
  67. public ValueNode(int key, int value) {
  68. this.key = key;
  69. this.value = value;
  70. }
  71. }
  72. }

2.LFU算法

LFU(least frequently used)最近最不经常使用的算法,对于每个数据,维护其使用的次数以及最近的使用时间,删除的策略是:优先删除使用次数最少的数据,如果存在多个使用次数相同的数据,则优先删除最远一次使用的数据。(描述有点拗口,可以这样理解:a,b两个个资源,其中a使用了5次,b使用了2次,则优先删除b,如果ab都使用了5次,a上次使用时5s前,b上次使用时2s前,则优先删除a)

2.1 LFU的数据结构

LFU数据结构相比较LRU会复杂很多:首先仍是key-value哈希表,这样set和get的时间复杂度都是O(1), 此外,还维护另外一个time-LinkList哈希表,这个哈希表用于存储每个key-value的使用次数,其中value是一个双向链表,维护着每个数据的使用顺序(类似LRU);

2.2 LFU算法思路

维护两个哈希表,其中key-value哈希表用于存储数据,key就是key,value记录key.value以及使用次数

time-value哈希表用于存储数据使用次数和使用先后顺序的链表。key为数据的使用次数,value是一个双向链表,记录着相同使用次数的数据使用顺序的先后。

读取数据:

key-value哈希表判断是否存在该数据,不存在在返回-1;如果存在,返回这个数据的值,同时在time-value哈希表进行维护:

  1. 将该数据从当前次数下的双向链表中删除,如果删除后链表为空,将链表从time-value哈希表中删除(key的值为原来的time);
  2. 将该数据插入当前次数+1的双向链表中,如果这个链表不存在,则创建一个新的双向链表,然后插入到time-value哈希表中(key的值是原来的time+1)

插入数据:

更新旧数据:

思路和读取数据基本一致:

  1. 将该数据从当前次数下的双向链表中删除,如果删除后链表为空,将链表从time-value哈希表中删除(key的值为原来的time);
  2. 将该数据插入当前次数+1的双向链表中,如果这个链表不存在,则创建一个新的双向链表,然后插入到time-value哈希表中(key的值是原来的time+1)
  3. key-value哈希表中更新数据

插入新数据:

  1. 插入数据前先判断是否需要删除数据 :如果需要删除,则获取time-value中使用频率最小的链表,删除链表最前端的数据。
  2. 添加新节点:
    1. 将新的数据插入到key-value哈希表中
    2. 判断key为1的数据在time-value哈希表中是否存在
      1. 存在:将该数据插入到链表中
      2. 不存在:创建一个新的链表,插入该数据,并在time-value哈希表插入该数据,其中key的值为1

维护time-value哈希表中key的最小值:

定义一个全局变量minTimes,分析什么时候对这个数据进行更新:

  1. 每次插入新的数据,minTimes的值要更新为1;
  2. 每次更新节点的访问频率时,如果原访问频率刚好是最小访问次数,并且更新完后原访问频率的对应链表为空,则minTimes要加1;

2.3 LFU算法代码

下面代码也是调试很久才跑通所有用例,代码结构未优化。

  1. package LRU_LFU;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class LFUCache {
  5. private Map<Integer, ValueNode> kvMap;
  6. private Map<Integer, LinkNodeList> timeMap;
  7. private int capacity;
  8. private int minTimes = 1;
  9. // 初始化cache,定义capacity
  10. public LFUCache(int capacity) {
  11. if (capacity <= 0) {
  12. return;
  13. }
  14. this.capacity = capacity;
  15. kvMap = new HashMap<>();
  16. timeMap = new HashMap<>();
  17. }
  18. // 从缓存中获取数据,如果不存在,则返回负一
  19. public int get(int key) {
  20. if (kvMap.containsKey(key)) {
  21. // 如果存在,则将该节点的访问次数加1,在timeMap中进行维护
  22. ValueNode node = kvMap.get(key);
  23. deleteNode4LinkList(node);
  24. node.count++;
  25. insertNode4LinkList(node);
  26. return node.value;
  27. } else {
  28. return -1;
  29. }
  30. }
  31. // 给链表中添加节点,然后维护timeMap
  32. private void insertNode4LinkList(ValueNode node) {
  33. LinkNodeList linkNodeList;
  34. if (timeMap.containsKey(node.count)) {
  35. linkNodeList = timeMap.get(node.count);
  36. } else {
  37. linkNodeList = new LinkNodeList();
  38. }
  39. linkNodeList.insertNode(node);
  40. timeMap.put(node.count, linkNodeList);
  41. }
  42. // 删除链表中的节点
  43. private void deleteNode4LinkList(ValueNode node) {
  44. LinkNodeList linkNodeList = timeMap.get(node.count);
  45. linkNodeList.deleteNode(node);
  46. if (linkNodeList.isEmpty()) {
  47. // 如果删除节点后链表为空,则将链表从timeMap中移除
  48. timeMap.remove(node.count);
  49. if (minTimes == node.count) {
  50. // 如果移除的链表key恰好是最小访问次数,则最小访问次数要加1
  51. this.minTimes = minTimes +1;
  52. }
  53. }
  54. }
  55. // 给缓存中加入数据
  56. public void put(int key, int value) {
  57. if (capacity <=0) {
  58. return;
  59. }
  60. if (kvMap.containsKey(key)) {
  61. // 如果存在则将该节点的访问次数加1,更新value,然后维护kvMap和timeMap
  62. ValueNode node = kvMap.get(key);
  63. deleteNode4LinkList(node);
  64. node.count++;
  65. node.value = value;
  66. insertNode4LinkList(node);
  67. kvMap.put(key, node);
  68. } else {
  69. if (kvMap.size() == capacity) {
  70. removeNodes();
  71. }
  72. ValueNode node = new ValueNode(key, value, 1);
  73. if (timeMap.containsKey(1)) {
  74. LinkNodeList linkNodeList = timeMap.get(1);
  75. linkNodeList.insertNode(node);
  76. timeMap.put(node.count, linkNodeList);
  77. } else {
  78. LinkNodeList linkNodeList = new LinkNodeList();
  79. linkNodeList.insertNode(node);
  80. timeMap.put(node.count, linkNodeList);
  81. this.minTimes = 1;
  82. }
  83. kvMap.put(key, node);
  84. }
  85. }
  86. // 清理最少使用次数的节点,如果次数相同,则清理最远一次操作的节点
  87. private void removeNodes() {
  88. LinkNodeList linkNodeList = timeMap.get(minTimes);
  89. ValueNode valueNode = linkNodeList.head.right;
  90. int key = valueNode.key;
  91. linkNodeList.deleteNode(valueNode);
  92. if (linkNodeList.isEmpty()) {
  93. timeMap.remove(minTimes);
  94. }
  95. kvMap.remove(key);
  96. }
  97. // 定义双向链表中的节点,包括键值,计数器,左右节点,构造器
  98. class ValueNode {
  99. // 定义键值
  100. int key;
  101. int value;
  102. int count;
  103. // 定义左右节点
  104. ValueNode left;
  105. ValueNode right;
  106. // 构造器
  107. public ValueNode(int key, int value, int count) {
  108. this.key = key;
  109. this.value = value;
  110. this.count = count;
  111. }
  112. }
  113. // 定义双向链表,维护两个虚拟节点,作为头节点和尾节点
  114. class LinkNodeList {
  115. private final ValueNode head;
  116. private final ValueNode tail;
  117. // 双向链表定义头和尾,并且头尾相连接。
  118. public LinkNodeList() {
  119. head = new ValueNode(-1, -1, 1);
  120. tail = new ValueNode(-1, -1, 1);
  121. head.right = tail;
  122. tail.left = head;
  123. }
  124. // 双向链表中插入节点,插入的位置在链表的末尾
  125. public void insertNode(ValueNode node) {
  126. ValueNode left = tail.left;
  127. node.right=tail;
  128. node.left=left;
  129. left.right=node;
  130. tail.left=node;
  131. }
  132. // 双向链表删除节点,删除的节点时是链表的头部
  133. public void deleteNode(ValueNode node) {
  134. ValueNode right = node.right;
  135. ValueNode left = node.left;
  136. left.right =right;
  137. right.left=left;
  138. node.right=null;
  139. node.left=null;
  140. }
  141. // 判断双向链表是否为空
  142. public boolean isEmpty() {
  143. return head.right == tail;
  144. }
  145. }
  146. }

此外,还有一种仿照LRU写LFU的思路,效率可能比上述要慢,就暂时不写了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/950748
推荐阅读
相关标签
  

闽ICP备14008679号