• 【数据结构与算法】通过双向链表和HashMap实现LRU缓存 详解


    这个双向链表采用的是有伪头节点和伪尾节点的 与上一篇文章中单链表的实现不同,区别于在实例化这个链表时就初始化了的伪头节点和伪尾节点,并相互指向,在第一次添加节点时,不需要再考虑空指针指向问题了。

    1. /**
    2. * 通过链表与HashMap实现LRU缓存
    3. *
    4. * @author CC
    5. * @version 1.0
    6. * @since2023/9/27
    7. */
    8. public class LRUCache {
    9. private Map cache = new HashMap<>();//哈希表
    10. private int size;//链表长度
    11. private int capacity;//缓存容量
    12. private Node first;//伪头节点
    13. private Node last;//伪尾节点
    14. /**
    15. * 将一个新节点添加到头部
    16. *
    17. * @param newNode 要添加的新节点
    18. */
    19. private void addFirst(Node newNode) {
    20. //注意: 顺序很重要
    21. //1、分配新节点的前驱和后继
    22. newNode.prev = first;
    23. newNode.next = first.next;
    24. //2、头节点原来的后继的前驱指向新节点
    25. first.next.prev = newNode;
    26. //3、头节点的后继执行新节点
    27. first.next = newNode;
    28. }
    29. /**
    30. * 删除一个节点
    31. *
    32. * @param node 要删除的节点
    33. */
    34. private void deleteNode(Node node) {
    35. //要删除节点的后继和前驱相互指引
    36. node.prev.next = node.next;
    37. node.next.prev = node.prev;
    38. }
    39. /**
    40. * 将一个节点放到伪头节点后
    41. *
    42. * @param node 移动的节点
    43. */
    44. private void moveToFirst(Node node) {
    45. //删除这个节点
    46. deleteNode(node);
    47. //添加一个头节点
    48. addFirst(node);
    49. }
    50. /**
    51. * 删除尾节点
    52. *
    53. * @return 返回删除的这个节点
    54. */
    55. private Node deleteToLast() {
    56. //获得伪尾节点的前驱 也就是尾节点
    57. Node ret = last.prev;
    58. //删除尾节点
    59. deleteNode(last.prev);
    60. return ret;
    61. }
    62. /**
    63. * 存入缓存
    64. *
    65. * @param key
    66. * @param value
    67. */
    68. public void put(int key, int value) {
    69. //从hash表中查询这个健
    70. Node node = cache.get(key);
    71. //如果hash表中不存在要添加的健
    72. if (node == null) {
    73. //创建一个新的节点
    74. Node newNode = new Node(key, value);
    75. //将这个健和节点添加到hash表中
    76. cache.put(key, newNode);
    77. //将这个节点存到头节点中
    78. addFirst(newNode);
    79. //如果这个缓存已满
    80. if (++size > capacity) {
    81. //删除尾节点
    82. Node last = deleteToLast();
    83. //从hash表中也删除这个健
    84. cache.remove(last.key);
    85. size--;
    86. }
    87. //如果hash表中存在要添加的健
    88. } else {
    89. //将新添加的值覆盖原来的值
    90. node.value = value;
    91. //并移到头节点
    92. moveToFirst(node);
    93. }
    94. }
    95. /**
    96. * 获取缓存
    97. *
    98. * @param key 该缓存的健
    99. * @return 返回 该节点的值
    100. */
    101. public int get(int key) {
    102. //通过健从hash表中获取这个节点
    103. Node node = cache.get(key);
    104. //如果为空 则返回-1
    105. if (node == null) {
    106. return -1;
    107. }
    108. //否则 将该节点 移到头节点处
    109. moveToFirst(node);
    110. return node.value;
    111. }
    112. /**
    113. * 双向链表的遍历 头->尾
    114. *
    115. * @return
    116. */
    117. @Override
    118. public String toString() {
    119. StringJoiner sj = new StringJoiner("->");
    120. for (Node n =first.next;n.next!=null;n=n.next){
    121. sj.add(String.valueOf(n.value));
    122. }
    123. return "头->尾:"+sj.toString();
    124. }
    125. /**
    126. * 构造方法
    127. *
    128. * @param capacity 设置缓存容量
    129. */
    130. public LRUCache(int capacity) {
    131. size = 0;//初始链表长度位0
    132. this.capacity = capacity;//设置缓存容量
    133. first = new Node();//实例化伪头节点
    134. last = new Node();//实例化伪尾节点
    135. //初始头尾节点相互指向
    136. first.next = last;
    137. last.prev = first;
    138. }
    139. /**
    140. * 节点类
    141. */
    142. class Node {
    143. int key; //键
    144. int value;//值
    145. Node prev;//前驱
    146. Node next;//后继
    147. /**
    148. * 无参构造
    149. */
    150. public Node() {
    151. }
    152. /**
    153. * 有参构造
    154. *
    155. * @param key 健
    156. * @param value 值
    157. */
    158. public Node(int key, int value) {
    159. this.key = key;
    160. this.value = value;
    161. }
    162. }
    163. }

    测试

    1. //实例一个缓存大小为7的LRU缓存
    2. LRUCache lruCache =new LRUCache(5);
    3. lruCache.put(1,1);
    4. lruCache.put(2,2);
    5. lruCache.put(3,3);
    6. lruCache.put(4,4);
    7. lruCache.put(5,5);
    8. lruCache.put(6,6);
    9. System.out.println("依次存入1、2、3、4、5、6后的缓存:"+lruCache);
    10. int l1 = lruCache.get(1);
    11. System.out.println("取出1后的缓存:"+lruCache+",取出的值:"+l1);
    12. int l2 = lruCache.get(2);
    13. System.out.println("取出2后的缓存:"+lruCache+",取出的值:"+l2);
    14. int l3 = lruCache.get(3);
    15. System.out.println("取出3后的缓存:"+lruCache+",取出的值:"+l3);
    16. lruCache.put(9,9);
    17. System.out.println("存入9后的缓存:"+lruCache);

    测试结果

  • 相关阅读:
    Docker 入门及实践
    FFmpeg源代码简单分析-其他-libswscale的sws_scale()
    多线程初阶(一)
    【python】系列之item.taobao 获取商品详情API接口调用
    【科普】蛮秃然的-大学生脱发科普
    消费增值模式:消费与盈利的完美结合
    基于Python的高校网络课程数据分析
    Visual Studio Code+drawio扩展插件的安装和使用,免费的软件构图神器
    多线程(基础)
    npm install / webdriver-manager update报错 unable to get local issuer certificate
  • 原文地址:https://blog.csdn.net/c1390527393/article/details/133359398