【redis前传】本身手写一个LRU策略 | 抓住时间的尾巴

这是我参与更文挑战的第23天,活动详情查看: 更文挑战node

1、题目描述

146. LRU 缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 实现 LRUCache 类:缓存

LRUCache(int capacity) 以正整数做为容量 capacity 初始化LRU缓存 int get(int key) 若是关键字 key 存在于缓存中,则返回关键字的值,不然返回 -1 。 void put(int key, int value) 若是关键字已经存在,则变动其数据值;若是关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据以前删除最久未使用的数据值,从而为新的数据值留出空间。markdown

进阶:你是否能够在 O(1) 时间复杂度内完成这两种操做?数据结构

image-20210611091820720

2、思路分析

第一想法

  • 刚看到本题时没有多想就以为会用到队列,由于队列FIFO能够作到淘汰末尾数据,可是仔细一想本题是须要淘汰最近最少使用数据,若是仅仅是最近的数据那么队列很容易实现。加上使用频率就涉及到数据的频繁挪动。很明显队列是没法完成的。
  • 那么有没有一种顺序添加的数据,每次在获取以后就会将数据前移至一端呢?答案是有的!LinkedHashMap
  • LinkedHashMap 不熟悉的朋友们能够简单的将它理解成HashMap 。 下图展现了HashMap的存储结构

image-20210611091747182

  • 上述的元素我这里作了个动画演示全过程!!!

动画演示

  • LinkedHashMap只是多了一条链表串起里面的元素

  • 这也是为何LinkedHashMap是按照顺序存储的。可是LinkedHahsMap也没法作到按照使用频率进行排序啊?你们都知道他是按照添加顺序排序的!!!

*LinkedHashMap*改造

  • 原生的LinkedHashMap的确没法知足状况,可是咱们稍微看下源码可以发如今put以后都会执行下afterNodeInsertion 这个方法。这也是HashMap留给LinkedHashMap作的扩展!

image-20210611095540549

image-20210611100631032

  • removeNode 就是将最前面的数据。想要进入这个方法就须要removeEldestEntry判断。LinkedHashMap默认是false . 因此咱们只须要重写他就好了。可是仍是在get值的时候如何保值在最后面呢?咱们仔细看下源码就可以发如今get中有这个一个方法afterNodeAccess 。他的做用就是将get的元素移位值后面。正好符合咱们LRU的策略特征

image-20210611101435521

  • 综上!咱们借助LinkedHashMap就很是容易的实现了LRU策略!

image-20210611101819720

本身实现

  • 可是本题的意思是想考察咱们本身是如何实现的,而不是巧妙对现有的工具改造的!不过上面对LinkedHashMap的确改造的很巧这是不能否认的!下面咱们就尝试本身来实现下这种方式!工具

  • 首先咱们须要肯定须要用到Hash结合链表来实现。Hash咱们天然使用HashMap来存储数据为的就是方便定位数据。定位到数据就须要操做链表将数据实时移位值链表尾部,每次淘汰是将链表首位移除既可。为了方便咱们操做链表这里的链表确定是双链表的!oop

链表单元

image-20210611104201991

  • 首先咱们定义一个内部类!用于链表的基本单元。里面存储了key,value方便根据Hash中存储的内容找到节点!preNodenextNode分别指向先后节点

image-20210611105808351

  • 在构建器中初始化容量和链表大小,并初始化边界节点方便咱们操做节点中移位和删除。

image-20210611105924364

  • 在获取数据时没有添加就返回-1 , 已经添加的数据则将该数据对应的node节点移动到链表的尾部。

image-20210611110148633

  • 在put中当第一次添加咱们须要维护链表大小并进行检测是否须要进行淘汰数据,若是不是第一次添加咱们只需奥更新值和对应node在链表中的位置便可

image-20210611110255277

方法名 做用
addToTail 将节点添加值链表尾部
moveToTail 将已经存在于链表中的节点移动到链表的尾部
removeHeadNode 删除链表中第一个节点,注意是边界节点后第一个节点

image-20210611110451514

4、总结

  • 虽然执行时间和内存消耗有点高!可是我就是不优化。
  • 本题主要就是在链表的移动上面会复杂点。咱们须要按照添加顺序和使用频率两个维度进行维护他们之间的顺序。只要这个顺序维护好,就没啥问题了!
相关文章
相关标签/搜索