当前位置:   article > 正文

内存替换算法:LRU和LFU超详细!!!_内存替换算法 最复杂

内存替换算法 最复杂

在我们内存或者cache中有两种替换算法来保证内存或者cache中都是“热点”的数据,一个是LRU和LFU。下面会先分析再给出代码!

在这里的get和set操作的时间复杂度都是O(1),因为这两种速度都是很快的,不能要求O(n)。

一、LRU

首先我们搞一个双向链表

我们定义这个规则:如果有节点被访问(get或者set),那么直接放入到末尾,每一个队列都有一个capacity值表示容量,如果新元素加入,现在达到了容量值,就需要淘汰最近最少使用的,就是头节点。例如:现在node1被访问。

如果现在又来了一个新元素noden+1,超过了容量值。所以:剔除node2,noden+1条件到末尾

这就是最近最少使用的算法,热点数据都在队尾。删除只删除头节点。

基础数据结构Node

  1. public static class Node<V> {
  2. public V value;
  3. public Node<V> pre;
  4. public Node<V> next;
  5. public Node(V value) {
  6. this.value = value;
  7. }
  8. }

双向队列

基本属性和构造函数

  1. public static class DoubleLinkedList<V> {
  2. private Node<V> head;
  3. private Node<V> tail;
  4. public DoubleLinkedList() {
  5. this.head = null;
  6. this.tail = null;
  7. }

下面我们来看方法:

  1. public void addNode(Node<V> newNode) {
  2. if (newNode == null) {
  3. return;
  4. }
  5. if (this.head == null) {
  6. this.head = newNode;
  7. this.tail = newNode;
  8. } else {
  9. this.tail.next = newNode;
  10. newNode.pre = this.tail;
  11. this.tail = newNode;
  12. }
  13. }

添加新节点,如果当前是第一个元素,头指针和尾指针都指向它,如果不是,放在队尾

  1. public void moveNodeToTail(Node<V> node) {
  2. if (this.tail == null) {
  3. return;
  4. }
  5. if (this.head == node) {
  6. this.head = node.next;
  7. this.head.pre = null;
  8. } else {
  9. node.pre.next = node.next;
  10. node.next.pre = node.pre;
  11. }
  12. this.tail.next = node;
  13. node.pre = this.tail;
  14. this.tail = node;
  15. this.tail.next = null;
  16. }

将一个节点移动到队尾,分为该节点是不是头节点,如果是头节点指向下一个,如果不是,就直接拿出来,然后放到队尾。

  1. public Node<V> removeHead() {
  2. if(this.head == null){
  3. return this.head;
  4. }
  5. Node<V> res = this.head;
  6. if(this.head == this.tail){
  7. this.head = null;
  8. this.tail = null;
  9. }else{
  10. this.head = res.next;
  11. res.next = null;
  12. this.head.pre = null;
  13. }
  14. return res;
  15. }

 删除头节点并返回。

Cache数据结构

基本属性和构造方法

  1. public static class MyCache<K,V>{
  2. private HashMap<K,Node<V>> keyNodeMap;
  3. private HashMap<Node<V>,K> nodeKeyMap;
  4. private DoubleLinkedList<V> nodeList;
  5. private int capacity;
  6. public MyCache(int capacity){
  7. if(capacity < 1){
  8. throw new RuntimeException();
  9. }
  10. this.nodeKeyMap = new HashMap<>();
  11. this.keyNodeMap = new HashMap<>();
  12. this.nodeList = new DoubleLinkedList<>();
  13. this.capacity = capacity;
  14. }

这里面用到了两个HashMap,为啥?因为hashmap的put和get的时间复杂度都是O(1),如果不用,遍历链表,那么我们的操作就是时间复杂度O(n),所以用到它俩主要是以空间换时间的方式提升效率!!!

get方法,获取value,判断key是否在map里面,如果再然后拿出节点,再移动到末尾。

  1. public V get(K key){
  2. if(this.keyNodeMap.containsKey(key)){
  3. Node<V> res = this.keyNodeMap.get(key);
  4. this.nodeList.moveNodeToTail(res);
  5. return res.value;
  6. }
  7. return null;
  8. }

set方法,判断是否有,如果有取出,移动到末尾,如果没有新建节点,加入,然后判断是否达到了容量,如果达到了容量就剔除头部节点。

  1. public void set(K key,V value){
  2. if(this.keyNodeMap.containsKey(key)){
  3. Node<V> target = keyNodeMap.get(key);
  4. target.value = value;
  5. this.nodeList.moveNodeToTail(target);
  6. }else{
  7. Node<V> node = new Node<>(value);
  8. this.keyNodeMap.put(key,node);
  9. this.nodeKeyMap.put(node,key);
  10. this.nodeList.addNode(node);
  11. if(this.keyNodeMap.size() == this.capacity + 1){
  12. removeMostCache();
  13. }
  14. }
  15. }
  1. private void removeMostCache(){
  2. Node<V> node = this.nodeList.removeHead();
  3. K removeKey = this.nodeKeyMap.get(node);
  4. this.keyNodeMap.remove(removeKey);
  5. this.nodeKeyMap.remove(node);
  6. }

这样就做到了get和set方法的时间复杂度O(1)。

二、LFU

最近最少使用算法,这个算法相比于LRU就有难度了。我们来看思路:两个数据结构。

小节点类型

  1. /**
  2. * 小节点类型
  3. */
  4. public static class Node {
  5. public Integer key;
  6. public Integer value;
  7. public Integer times;
  8. public Node up;
  9. public Node down;
  10. public Node(int key, int value, int times) {
  11. this.key = key;
  12. this.value = value;
  13. this.times = times;
  14. }
  15. }

大节点类型 

  1. /**
  2. * 大节点类型
  3. */
  4. public static class NodeList {
  5. public Node head;
  6. public Node tail;
  7. public NodeList pre;
  8. public NodeList next;
  9. public NodeList(Node node) {
  10. this.head = node;
  11. this.tail = node;
  12. }

实际上就是一个下面的结构,矩形表示NodeList,圆形表示Node。 

我们的数据是字母,上面矩形表示访问(get和set方法)的次数。先解释一下我们的规则:

(1)NodeList只有在下面有节点的时候才会被创建,如果下面的节点被移除,那么删除该NodeList。

(2)NodeList中head和tail分别指向下面圆形双向链表的头跟尾部。他本身也是一个双向链表。

(3)如果现在有一个新节点W被访问。插入到1下面的双向链表的首部

如果此时A被访问:就到2下面的双向链表的首部。

如果A再次被访问:

如果此时2被访问。2下面没有元素了,就删除2,然后c放到3上。

如果此时A再次被访问,那么创建4的NodeList,然后插入A:

如果这个时候来了一个H,但是现在达到了最大容量,就要剔除一个,我们剔除1下面双向链表的尾节点。

这个NodeList的头节点的node双向链表的尾节点就是最近最少使用的节点。以上就是整个LFU的过程,下面我们来看代码!

  1. /**
  2. * 大节点类型
  3. */
  4. public static class NodeList {
  5. public Node head;
  6. public Node tail;
  7. public NodeList pre;
  8. public NodeList next;
  9. public NodeList(Node node) {
  10. this.head = node;
  11. this.tail = node;
  12. }
  13. /**
  14. * 新到的节点放在头部位置
  15. */
  16. public void addNodeFromHead(Node newNode) {
  17. newNode.down = this.head;
  18. this.head.up = newNode;
  19. head = newNode;
  20. }
  21. public boolean isEmpty() {
  22. return head == null;
  23. }
  24. /**
  25. * 移除节点到下一个NodeList。
  26. */
  27. public void deleteNode(Node node) {
  28. if (this.head == this.tail) {
  29. this.head = null;
  30. this.tail = null;
  31. } else {
  32. if (node == head) {
  33. head = node.down;
  34. head.up = null;
  35. } else if (node == tail) {
  36. tail = node.up;
  37. tail.down = null;
  38. } else {
  39. node.up.down = node.down;
  40. node.down.up = node.up;
  41. }
  42. }
  43. node.up = null;
  44. node.down = null;
  45. }
  46. }

在NodeList里面我们提供了三个方法:

(1)addNodeFromHead:将新加入的节点放在头部位置,保证无论进入哪个NodeList都必须保证在头部位置。

(2)isEmpty:判断当前NodeList是否为null。

(3)deleteNode:就是将访问的节点,从该NodeList移除,这个节点可以是头尾节点或者中间节点。

  1. public static class LFUCache {
  2. private int capacity;
  3. private int size;
  4. //key => node
  5. private HashMap<Integer, Node> records;
  6. //存储node的nodelist节点
  7. private HashMap<Node, NodeList> heads;
  8. //nodelist头节点
  9. private NodeList headList;
  10. public LFUCache(int capacity) {
  11. this.capacity = capacity;
  12. this.size = 0;
  13. this.records = new HashMap<>();
  14. this.heads = new HashMap<>();
  15. this.headList = null;
  16. }

这里面有几个属性:

(1)capacity:表示最大容量

(2)size:表示当前大小。

(3)records:表示key和node,这里的key是Integer,value是node类型

(4)heads:表示的是Node的上层NodeList节点是哪个。

(5)headList:表示所有NodeList的双向链表的头一个。

下面看方法:

判断如果有这元素,就更新数据同时times次数+1。然后执行move方法移动到下一个节点。move方法后面会有,如果没在并且达到了容量,就剔除一个节点,然后放入一个新节点

  1. public void set(int key, int value) {
  2. if (records.containsKey(key)) {
  3. Node node = records.get(key);
  4. node.value = value;
  5. node.times++;
  6. NodeList curNodeList = heads.get(node);
  7. //讲目标节点移动到下一个NodeList
  8. move(node, curNodeList);
  9. } else {
  10. //达到容量
  11. if (size == capacity) {
  12. Node node = headList.tail;
  13. headList.deleteNode(node);
  14. //是否删除这个头
  15. modifyHeadList(headList);
  16. records.remove(node.key);
  17. heads.remove(node);
  18. size--;
  19. }
  20. Node node = new Node(key, value, 1);
  21. //刚开始加入第一个元素
  22. if (headList == null) {
  23. headList = new NodeList(node);
  24. } else {
  25. if (headList.head.times.equals(node.times)) {
  26. headList.addNodeFromHead(node);
  27. } else {
  28. NodeList newList = new NodeList(node);
  29. newList.next = headList;
  30. headList.pre = newList;
  31. headList = newList;
  32. }
  33. }
  34. records.put(key, node);
  35. heads.put(node, headList);
  36. size++;
  37. }
  38. }

剔除元素之后,要判断这个NodeList是不是没有node了,入股没有就删除,否则留下。

  1. /**
  2. * 如果当前nodelist为空,就更换headList,否则就不
  3. */
  4. private boolean modifyHeadList(NodeList nodeList) {
  5. if (nodeList.isEmpty()) {
  6. if (headList == nodeList) {
  7. headList = nodeList.next;
  8. if (headList != null) {
  9. headList.pre = null;
  10. }
  11. } else {
  12. nodeList.pre.next = nodeList.next;
  13. if (nodeList.next != null) {
  14. nodeList.next.pre = nodeList.pre;
  15. }
  16. }
  17. return true;
  18. }
  19. return false;
  20. }

移动节点到新的NodeList

  1. private void move(Node node, NodeList oldNodeList) {
  2. oldNodeList.deleteNode(node);
  3. //拿到前一个节点的NodeList
  4. NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.pre : oldNodeList;
  5. NodeList nextList = oldNodeList.next;
  6. if (nextList == null) {
  7. NodeList newList = new NodeList(node);
  8. if (preList != null) {
  9. preList.next = newList;
  10. }
  11. newList.pre = preList;
  12. if (headList == null) {
  13. headList = newList;
  14. }
  15. heads.put(node, newList);
  16. } else {
  17. if (nextList.head.times.equals(node.times)) {
  18. nextList.addNodeFromHead(node);
  19. heads.put(node, nextList);
  20. } else {
  21. NodeList newList = new NodeList(node);
  22. if (preList != null) {
  23. preList.next = newList;
  24. }
  25. newList.pre = preList;
  26. newList.next = nextList;
  27. nextList.pre = newList;
  28. if (headList == nextList) {
  29. headList = newList;
  30. }
  31. heads.put(node, newList);
  32. }
  33. }
  34. }

get方法:

  1. public int get(int key) {
  2. if (!records.containsKey(key)) {
  3. return -1;
  4. }
  5. Node node = records.get(key);
  6. node.times++;
  7. NodeList curNodeList = heads.get(node);
  8. move(node, curNodeList);
  9. return node.value;
  10. }

完整代码:

LFU:

  1. public class LFU {
  2. /**
  3. * 小节点类型
  4. */
  5. public static class Node {
  6. public Integer key;
  7. public Integer value;
  8. public Integer times;
  9. public Node up;
  10. public Node down;
  11. public Node(int key, int value, int times) {
  12. this.key = key;
  13. this.value = value;
  14. this.times = times;
  15. }
  16. }
  17. /**
  18. * 大节点类型
  19. */
  20. public static class NodeList {
  21. public Node head;
  22. public Node tail;
  23. public NodeList pre;
  24. public NodeList next;
  25. public NodeList(Node node) {
  26. this.head = node;
  27. this.tail = node;
  28. }
  29. /**
  30. * 新到的节点放在头部位置
  31. */
  32. public void addNodeFromHead(Node newNode) {
  33. newNode.down = this.head;
  34. this.head.up = newNode;
  35. head = newNode;
  36. }
  37. public boolean isEmpty() {
  38. return head == null;
  39. }
  40. /**
  41. * 移除节点到下一个NodeList。
  42. */
  43. public void deleteNode(Node node) {
  44. if (this.head == this.tail) {
  45. this.head = null;
  46. this.tail = null;
  47. } else {
  48. if (node == head) {
  49. head = node.down;
  50. head.up = null;
  51. } else if (node == tail) {
  52. tail = node.up;
  53. tail.down = null;
  54. } else {
  55. node.up.down = node.down;
  56. node.down.up = node.up;
  57. }
  58. }
  59. node.up = null;
  60. node.down = null;
  61. }
  62. }
  63. public static class LFUCache {
  64. private int capacity;
  65. private int size;
  66. //key => node
  67. private HashMap<Integer, Node> records;
  68. //存储node的nodelist节点
  69. private HashMap<Node, NodeList> heads;
  70. //nodelist头节点
  71. private NodeList headList;
  72. public LFUCache(int capacity) {
  73. this.capacity = capacity;
  74. this.size = 0;
  75. this.records = new HashMap<>();
  76. this.heads = new HashMap<>();
  77. this.headList = null;
  78. }
  79. public void set(int key, int value) {
  80. if (records.containsKey(key)) {
  81. Node node = records.get(key);
  82. node.value = value;
  83. node.times++;
  84. NodeList curNodeList = heads.get(node);
  85. //讲目标节点移动到下一个NodeList
  86. move(node, curNodeList);
  87. } else {
  88. //达到容量
  89. if (size == capacity) {
  90. Node node = headList.tail;
  91. headList.deleteNode(node);
  92. //是否删除这个头
  93. modifyHeadList(headList);
  94. records.remove(node.key);
  95. heads.remove(node);
  96. size--;
  97. }
  98. Node node = new Node(key, value, 1);
  99. //刚开始加入第一个元素
  100. if (headList == null) {
  101. headList = new NodeList(node);
  102. } else {
  103. if (headList.head.times.equals(node.times)) {
  104. headList.addNodeFromHead(node);
  105. } else {
  106. NodeList newList = new NodeList(node);
  107. newList.next = headList;
  108. headList.pre = newList;
  109. headList = newList;
  110. }
  111. }
  112. records.put(key, node);
  113. heads.put(node, headList);
  114. size++;
  115. }
  116. }
  117. public int get(int key) {
  118. if (!records.containsKey(key)) {
  119. return -1;
  120. }
  121. Node node = records.get(key);
  122. node.times++;
  123. NodeList curNodeList = heads.get(node);
  124. move(node, curNodeList);
  125. return node.value;
  126. }
  127. /**
  128. * 如果当前nodelist为空,就更换headList,否则就不
  129. */
  130. private boolean modifyHeadList(NodeList nodeList) {
  131. if (nodeList.isEmpty()) {
  132. if (headList == nodeList) {
  133. headList = nodeList.next;
  134. if (headList != null) {
  135. headList.pre = null;
  136. }
  137. } else {
  138. nodeList.pre.next = nodeList.next;
  139. if (nodeList.next != null) {
  140. nodeList.next.pre = nodeList.pre;
  141. }
  142. }
  143. return true;
  144. }
  145. return false;
  146. }
  147. private void move(Node node, NodeList oldNodeList) {
  148. oldNodeList.deleteNode(node);
  149. //拿到前一个节点的NodeList
  150. NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.pre : oldNodeList;
  151. NodeList nextList = oldNodeList.next;
  152. if (nextList == null) {
  153. NodeList newList = new NodeList(node);
  154. if (preList != null) {
  155. preList.next = newList;
  156. }
  157. newList.pre = preList;
  158. if (headList == null) {
  159. headList = newList;
  160. }
  161. heads.put(node, newList);
  162. } else {
  163. if (nextList.head.times.equals(node.times)) {
  164. nextList.addNodeFromHead(node);
  165. heads.put(node, nextList);
  166. } else {
  167. NodeList newList = new NodeList(node);
  168. if (preList != null) {
  169. preList.next = newList;
  170. }
  171. newList.pre = preList;
  172. newList.next = nextList;
  173. nextList.pre = newList;
  174. if (headList == nextList) {
  175. headList = newList;
  176. }
  177. heads.put(node, newList);
  178. }
  179. }
  180. }
  181. }
  182. }

LRU:

  1. package day_05;
  2. import java.util.HashMap;
  3. /**
  4. * @ClassName LRU
  5. * @Description
  6. * @Author 戴书博
  7. * @Date 2020/6/13 20:18
  8. * @Version 1.0
  9. **/
  10. public class LRU {
  11. public static class Node<V> {
  12. public V value;
  13. public Node<V> pre;
  14. public Node<V> next;
  15. public Node(V value) {
  16. this.value = value;
  17. }
  18. }
  19. public static class DoubleLinkedList<V> {
  20. private Node<V> head;
  21. private Node<V> tail;
  22. public DoubleLinkedList() {
  23. this.head = null;
  24. this.tail = null;
  25. }
  26. public void addNode(Node<V> newNode) {
  27. if (newNode == null) {
  28. return;
  29. }
  30. if (this.head == null) {
  31. this.head = newNode;
  32. this.tail = newNode;
  33. } else {
  34. this.tail.next = newNode;
  35. newNode.pre = this.tail;
  36. this.tail = newNode;
  37. }
  38. }
  39. public void moveNodeToTail(Node<V> node) {
  40. if (this.tail == null) {
  41. return;
  42. }
  43. if (this.head == node) {
  44. this.head = node.next;
  45. this.head.pre = null;
  46. } else {
  47. node.pre.next = node.next;
  48. node.next.pre = node.pre;
  49. }
  50. this.tail.next = node;
  51. node.pre = this.tail;
  52. this.tail = node;
  53. this.tail.next = null;
  54. }
  55. public Node<V> removeHead() {
  56. if(this.head == null){
  57. return this.head;
  58. }
  59. Node<V> res = this.head;
  60. if(this.head == this.tail){
  61. this.head = null;
  62. this.tail = null;
  63. }else{
  64. this.head = res.next;
  65. res.next = null;
  66. this.head.pre = null;
  67. }
  68. return res;
  69. }
  70. }
  71. public static class MyCache<K,V>{
  72. private HashMap<K,Node<V>> keyNodeMap;
  73. private HashMap<Node<V>,K> nodeKeyMap;
  74. private DoubleLinkedList<V> nodeList;
  75. private int capacity;
  76. public MyCache(int capacity){
  77. if(capacity < 1){
  78. throw new RuntimeException();
  79. }
  80. this.nodeKeyMap = new HashMap<>();
  81. this.keyNodeMap = new HashMap<>();
  82. this.nodeList = new DoubleLinkedList<>();
  83. this.capacity = capacity;
  84. }
  85. public V get(K key){
  86. if(this.keyNodeMap.containsKey(key)){
  87. Node<V> res = this.keyNodeMap.get(key);
  88. this.nodeList.moveNodeToTail(res);
  89. return res.value;
  90. }
  91. return null;
  92. }
  93. public void set(K key,V value){
  94. if(this.keyNodeMap.containsKey(key)){
  95. Node<V> target = keyNodeMap.get(key);
  96. target.value = value;
  97. this.nodeList.moveNodeToTail(target);
  98. }else{
  99. Node<V> node = new Node<>(value);
  100. this.keyNodeMap.put(key,node);
  101. this.nodeKeyMap.put(node,key);
  102. this.nodeList.addNode(node);
  103. if(this.keyNodeMap.size() == this.capacity + 1){
  104. removeMostCache();
  105. }
  106. }
  107. }
  108. private void removeMostCache(){
  109. Node<V> node = this.nodeList.removeHead();
  110. K removeKey = this.nodeKeyMap.get(node);
  111. this.keyNodeMap.remove(removeKey);
  112. this.nodeKeyMap.remove(node);
  113. }
  114. }
  115. }

 

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

闽ICP备14008679号