自己实现本地缓存
前言
在扩展框架中的缓存组件相关功能时浏览了本地缓存的实现,其核心是LUR算法,记录下LUR的相关内容加深记忆和理解
环境
- windows 10
- jdk7
LUR
LRU全称是Least Recently Used,即最近最久未使用的意思。 LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
思路分析
对cache的基本操作有:插入(insert)、替换(replace)、查找(lookup)
为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们使用双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。
- 插入:当Cache未满时,新的数据项只需插到双链表头部即可。时间复杂度为O(1).
- 替换:当Cache已满时,将新的数据项插到双链表头部,并删除双链表的尾结点即可。时间复杂度为O(1).
- 查找:每次数据项被查询到时,都将此数据项移动到链表头部。
经过分析,我们知道使用双向链表可以保证插入和替换的时间复杂度是O(1),但查询的时间复杂度是O(n),因为需要对双链表进行遍历。为了让查找效率也达到O(1),很自然的会想到使用 hash table 。
代码实现
从上述分析可知,我们需要使用两种数据结构:
- 双向链表(Doubly Linked List)
- 哈希表(Hash Table)
Java中的LinkedHashMap正好满足这两种数据结构,直接用LinkedHashMap来实现(框架代码就是基于LinkedHashMap实现的)
1 | public class LRUCache<K, V> extends LinkedHashMap<K, V> { |
核心实现只需要继承LinkedHashMap,扩展它的removeEldestEntry方法即可,在多线程环境使用时使用 Collections.synchronizedMap()方法实现线程安全操作。框架中的细节代码就很多了。
简单测试
1 | public static void main(String[] args) { |
输出结果:
1 | 1:1 |
观察结果当超出最大容量自动删除了最久未使用的数据,访问过的数据排到了链表前端,符合预期
结语
了解原理后不管是解决问题还是重复造轮子都会得心应手。但不推荐重复造轮子,google guava cache 就是基于LUR算法实现的一个基础类库。