使用链表实现LRU cache
LRU(Least Recently Used,最近最少使用)算法是一种淘汰策略,简单来讲实现的是如下工作:将一些元素放在一个容量固定的容器中进行存取,由于容器的容量有限,该容器就要保证那些最近才被用到的元素始终在容器内,而将已经很久没有用的元素剔除,实现容器内元素的动态维护。这种算法是一种缓存维护策略,因为缓存空间有限,让缓存中存储的都是最近才被用到的元素可以实现系统缓存的高效运作。
结构与设计
需求
实现一个类LRUCache。
1.实现类的构造方法,使得该类的使用者可以通过设置初始化容量来创建一个指定容量的LRUCache。
2.实现该类对象的外部操作接口 —— get()成员函数。该函数的作用是查找一个指定的关键字是否在LRUCache对象中,如果在,返回该关键字的值,否则返回-1。即输入参数为一个int值key,返回一个int,代表LRUCache中关键字key对应的值。
3.实现该类对象的外部操作接口 —— put()成员函数。该函数的作用是将一个指定的键值对放入LRUCache对象中,该函数不需要向外返回任何值。即输入参数为一个int值key和一个int值value(代表一个键值对key-value),如果LRUCache中已存在该关键字key,则用参数列表中的value值更新LRUCache中key关键字对应的value,如果缓存中不存在该关键字key,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过缓存的初始容量 capacity ,则应该从缓存中删除最久未使用的关键字。
设计
根据以上要求,我们可以确定:
LRUCache本身需要用一个具体的存储容器来实现,该容器内部存储的元素形式是键值对的形式,并且该容器查找元素是通过输入键,返回键所对应的值来实现的,因此可以确定,LRUCache容器本身可以使用哈希表这种数据结构来实现。并且根据键与值的参数类型可以确定,该哈希表的键和值都是int类型。
LRUCache的int get(int key)接口完成的操作是哈希表本身就可以完成的操作。同时,该接口定义了缓存中元素被使用的含义,即最近一次通过get接口被查询到的键值对元素是缓存中最近一次被使用到的元素。
LRUCache的void put(int key, int value)接口完成的操作隐含了动态维护LRUCache中元素的需求。动态维护的方法就是LRU算法,即将LRUCache中的元素(int键值对)按照使用的时间顺序排列。同时需要特别注意的是,该接口操作也定义了缓存中元素另一种被使用的含义,即最近一次通过put接口被放入缓存中的元素是缓存中最近一次被使用到的元素。
可以通过双向链表来实现LRU算法,细节如下:双向链表的节点存储内容是用来实现LRUCache容器的哈希表元素(int类型键值对)
双向链表存在界限,即最大节点数就是LRUCache的容量。
每次通过int get(int key)接口查询到LRUCache中的一个元素,就将该元素在双向链表中移动到头部。
操作LRUCache的void put(int key, int value)接口可能会使得双向链表的节点数增加(当缓存中没有该key值,且缓存容量未满时),每次put操作(无论是存在key,只是更新value,还是不存在key,插入新的key-value对)都会将该put操作的key-value元素在双向链表中移动到头部
通过上述操作,双向链表的尾节点一定是LRUCache中最久没有被使用过的元素,则当双向链表超出限定长度后,删除超长的尾节点
要实现以上的双向链表操作,需要自己定义双向链表节点和相应的节点移动操作,在C++中可以通过自定义一个结构体或类来实现,其成员属性如下:
算法实现
1 | import java.util.HashMap; |