缓存的数据结构采用哈希表,key到value的映射。
网上有些资料采用记录数据的使用时刻 实现LRU策略,此处采用双向链表 实现LRU策略。LRU Least Recently Used,MRU Most Recently Used
双向链表,lruPtr头指向最近最少使用的元素,mruPtr头指向最近最多使用的元素。
LRUCache<int, int> tc(3); //最大三个元素
tc.insert(1, 101);
tc.insert(2, 102);
tc.insert(3, 103);
最终存储结构如下图:
哈希表中的元素:
- 黄色是 key域,哈希的键
- 蓝色是value域,存储的数据值
- 红色是newerPtr 域,指向下一个更新的 哈希项
- 绿色是oldPtr域,指向前一个更旧的 哈希项
get(key):若缓存中存在key,返回对应的value,否则返回-1
set(key,value):若缓存中存在key,替换其value,否则插入key及其value,如果插入时缓存已经满了,应该使用LRU算法把最近最久没有使用的key踢出缓存。
设计1:
cache使用数组,每个key再关联一个时间戳,时间戳可以直接用个long long类型表示,在cache中维护一个最大的时间戳:
- get的时候把key的时间戳变为最大时间戳+1
- set的时候,数据从前往后存储
如果key存在,更新key的时间戳为当前cache中最大的时间戳+1,并更新value;
如果key不存在,
若缓存满,在整个缓存中查找时间戳最小的key,其存储位置作为新key的存储位置,设置key的时间戳为最大时间戳+1
若缓存未满,设置key的时间戳为最大时间戳+1,存储位置为第一个空闲位置
分析下时间空间复杂度,get的时候,需要从前往后找key,时间为O(N),set的时候,也要从前往后找key,当缓存满的时候,还得找到时间戳最小的key,时间复杂度为O(N)。除了缓存本身,并没有使用其他空间,空间复杂度为O(1)。 这个速度显然是比较慢的,随着数据量的增大,get和set速度越来越慢。可能有人会想到用哈希表作为底层存储,这样get的时间复杂度确实可以减低为O(1),set的时候,只要缓存没有满,也可以在O(1)的时间完,但在缓存满的时候,依然需要每次遍历找时间戳最小的key,时间复杂度还是O(N)。
设计2:
cache底层使用单链表,同时用一个哈希表存储每个key对应的链表结点的前驱结点,并记录链表尾结点的key
- get时,从哈希表中找到key对应的链表结点,挪到链表头,更新指向尾结点的key
- set时,如果key存在,那么找到链表结点,并挪到链表头,更新指向尾结点的key
如果key不存在,
若缓存满,重用链表尾结点,设置新key和value,并挪到链表头,更新指向尾结点的key
若缓存未满,直接插入结点到链表头,若是第一结点,更新指向尾结点的key
get,set时间复杂度O(1),总的空间复杂度O(N)。比前面的设计好一点。下面的再来看下关于设计2的两个实现
实现1,自定义链表
为了方便链表的插入与删除,使用了带头结点head的链表,所以真正有效的第一个结点是head->next。另外,只是简单的实现,没有容错,不支持并发,简单的内存管理