设为首页 - 加入收藏 92站长网 (http://www.92zhanzhang.com)- 国内知名站长资讯网站,提供最新最全的站长资讯,创业经验,网站建设等!
热搜: 中国 苹果 系统 一些
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

不吹不黑,这个算法,你肯定不会

发布时间:2019-11-05 00:25 所属栏目:[优化] 来源:小黑
导读:01、前言 我们常用缓存提升数据查询速度,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,这样新数据才可以添加进来。缓存数据不能随机删除,一般情况下我们需要根据某种算法删除缓存数据。常用淘汰算法有 LRU,LFU,FIFO,这篇文章我们

不吹不黑,这个算法,你肯定不会

01、前言

我们常用缓存提升数据查询速度,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,这样新数据才可以添加进来。缓存数据不能随机删除,一般情况下我们需要根据某种算法删除缓存数据。常用淘汰算法有 LRU,LFU,FIFO,这篇文章我们聊聊 LRU 算法。

02、LRU 简介

LRU 是 Least Recently Used 的缩写,这种算法认为最近使用的数据是热门数据,下一次很大概率将会再次被使用。而最近很少被使用的数据,很大概率下一次不再用到。当缓存容量满的时候,优先淘汰最近很少使用的数据。

假设现在缓存内部数据如图所示:

不吹不黑,这个算法,你肯定不会

这里我们将列表第一个节点称为头结点,最后一个节点为尾结点。

当调用缓存获取 key=1 的数据,LRU 算法需要将 1 这个节点移动到头结点,其余节点不变,如图所示。

不吹不黑,这个算法,你肯定不会

然后我们插入一个 key=8 节点,此时缓存容量到达上限,所以加入之前需要先删除数据。由于每次查询都会将数据移动到头结点,未被查询的数据就将会下沉到尾部节点,尾部的数据就可以认为是最少被访问的数据,所以删除尾结点的数据。

不吹不黑,这个算法,你肯定不会

然后我们直接将数据添加到头结点。

不吹不黑,这个算法,你肯定不会

这里总结一下 LRU 算法具体步骤:

  • 新数据直接插入到列表头部
  • 缓存数据被命中,将数据移动到列表头部
  • 缓存已满的时候,移除列表尾部数据。

03、LRU 算法实现

上面例子中可以看到,LRU 算法需要添加头节点,删除尾结点。而链表添加节点/删除节点时间复杂度 O(1),非常适合当做存储缓存数据容器。但是不能使用普通的单向链表,单向链表有几点劣势:

  1. 每次获取任意节点数据,都需要从头结点遍历下去,这就导致获取节点复杂度为 O(N)。
  2. 移动中间节点到头结点,我们需要知道中间节点前一个节点的信息,单向链表就不得不再次遍历获取信息。

针对以上问题,可以结合其他数据结构解决。

使用散列表存储节点,获取节点的复杂度将会降低为 O(1)。节点移动问题可以在节点中再增加前驱指针,记录上一个节点信息,这样链表就从单向链表变成了双向链表。

综上使用双向链表加散列表结合体,数据结构如图所示:

不吹不黑,这个算法,你肯定不会

在双向链表中特意增加两个『哨兵』节点,不用来存储任何数据。使用哨兵节点,增加/删除节点的时候就可以不用考虑边界节点不存在情况,简化编程难度,降低代码复杂度。

LRU 算法实现代码如下,为了简化 key ,val 都认为 int 类型。

  1. public?class?LRUCache?{?
  2. ?
  3. ?Entry?head,?tail;?
  4. ?int?capacity;?
  5. ?int?size;?
  6. ?Map?cache;?
  7. ?
  8. ?
  9. ?public?LRUCache(int?capacity)?{?
  10. ?this.capacity?=?capacity;?
  11. ?//?初始化链表?
  12. ?initLinkedList();?
  13. ?size?=?0;?
  14. ?cache?=?new?HashMap<>(capacity?+?2);?
  15. ?}?
  16. ?
  17. ?/**?
  18. ?*?如果节点不存在,返回?-1.如果存在,将节点移动到头结点,并返回节点的数据。?
  19. ?*?
  20. ?*?@param?key?
  21. ?*?@return?
  22. ?*/?
  23. ?public?int?get(int?key)?{?
  24. ?Entry?node?=?cache.get(key);?
  25. ?if?(node?==?null)?{?
  26. ?return?-1;?
  27. ?}?
  28. ?//?存在移动节点?
  29. ?moveToHead(node);?
  30. ?return?node.value;?
  31. ?}?
  32. ?
  33. ?/**?
  34. ?*?将节点加入到头结点,如果容量已满,将会删除尾结点?
  35. ?*?
  36. ?*?@param?key?
  37. ?*?@param?value?
  38. ?*/?
  39. ?public?void?put(int?key,?int?value)?{?
  40. ?Entry?node?=?cache.get(key);?
  41. ?if?(node?!=?null)?{?
  42. ?node.value?=?value;?
  43. ?moveToHead(node);?
  44. ?return;?
  45. ?}?
  46. ?//?不存在。先加进去,再移除尾结点?
  47. ?//?此时容量已满?删除尾结点?
  48. ?if?(size?==?capacity)?{?
  49. ?Entry?lastNode?=?tail.pre;?
  50. ?deleteNode(lastNode);?
  51. ?cache.remove(lastNode.key);?
  52. ?size--;?
  53. ?}?
  54. ?//?加入头结点?
  55. ?
  56. ?Entry?newNode?=?new?Entry();?
  57. ?newNode.key?=?key;?
  58. ?newNode.value?=?value;?
  59. ?addNode(newNode);?
  60. ?cache.put(key,?newNode);?
  61. ?size++;?
  62. ?
  63. ?}?
  64. ?
  65. ?private?void?moveToHead(Entry?node)?{?
  66. ?//?首先删除原来节点的关系?
  67. ?deleteNode(node);?
  68. ?addNode(node);?
  69. ?}?
  70. ?
  71. ?private?void?addNode(Entry?node)?{?
  72. ?head.next.pre?=?node;?
  73. ?node.next?=?head.next;?
  74. ?
  75. ?node.pre?=?head;?
  76. ?head.next?=?node;?
  77. ?}?
  78. ?
  79. ?private?void?deleteNode(Entry?node)?{?
  80. ?node.pre.next?=?node.next;?
  81. ?node.next.pre?=?node.pre;?
  82. ?}?
  83. ?
  84. ?
  85. ?public?static?class?Entry?{?
  86. ?public?Entry?pre;?
  87. ?public?Entry?next;?
  88. ?public?int?key;?
  89. ?public?int?value;?
  90. ?
  91. ?public?Entry(int?key,?int?value)?{?
  92. ?this.key?=?key;?
  93. ?this.value?=?value;?
  94. ?}?
  95. ?
  96. ?public?Entry()?{?
  97. ?}?
  98. ?}?
  99. ?
  100. ?private?void?initLinkedList()?{?
  101. ?head?=?new?Entry();?
  102. ?tail?=?new?Entry();?
  103. ?
  104. ?head.next?=?tail;?
  105. ?tail.pre?=?head;?
  106. ?
  107. ?}?
  108. ?
  109. ?public?static?void?main(String[]?args)?{?
  110. ?
  111. ?LRUCache?cache?=?new?LRUCache(2);?
  112. ?
  113. ?cache.put(1,?1);?
  114. ?cache.put(2,?2);?
  115. ?System.out.println(cache.get(1));?
  116. ?cache.put(3,?3);?
  117. ?System.out.println(cache.get(2));?
  118. ?
  119. ?}?
  120. }?

04、LRU 算法分析

缓存命中率是缓存系统的非常重要指标,如果缓存系统的缓存命中率过低,将会导致查询回流到数据库,导致数据库的压力升高。

结合以上分析 LRU 算法优缺点。

LRU 算法优势在于算法实现难度不大,对于对于热点数据, LRU 效率会很好。

LRU 算法劣势在于对于偶发的批量操作,比如说批量查询历史数据,就有可能使缓存中热门数据被这些历史数据替换,造成缓存污染,导致缓存命中率下降,减慢了正常数据查询。

05、LRU 算法改进方案

以下方案来源于 MySQL InnoDB LRU 改进算法

【免责声明】本站内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

网友评论
推荐文章