title: LRU缓存机制Go语言实现
date: 2021-05-23 23:29:40
tags:

  • go

LRU缓存机制

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

设计思路

LRU算法需要对数据结构进行查询和插入、删除操作,对序列化数据结构来说,一般采取数组/链表的数据结构。两者差异对比如下:

  • 数组 查询比较快,但是对于增删来说是一个不是一个好的选择
  • 链表 查询比较慢,但是对于增删来说十分方便,时间复杂度为O(1)

因此,要想实现快速查询、增删,可以使用hash+链表的形式。hash表存储链表节点,可以在O(1)时间复杂度内查找;链表保持表头的节点是最近使用的节点,表尾的节点是最久为使用节点,这样在淘汰页面的时候直接将链表尾的节点淘汰就可以了,每次访问页面后将其放在链表头。

代码实现

Golang中的双向链表在container/list这个包下,Go语言标准库中文文档中有详细的介绍,这里不再赘述。

hash表可以直接使用Golang提供的map,这里定义为map[int]*list.Element类型。其中keyint型,表示页面号,*list.Element表示双向链表的一个节点。

  1. package LRU
  2. import "container/list"
  3. type LRUCache struct {
  4. Capacity int
  5. DoubleList *list.List
  6. Hashtable map[int]*list.Element
  7. }
  8. func New(cap int) *LRUCache {
  9. return &LRUCache{
  10. Capacity: cap,
  11. DoubleList: list.New(),
  12. Hashtable: make(map[int]*list.Element),
  13. }
  14. }
  15. func (l *LRUCache) Get(key int) int {
  16. v, ok := l.Hashtable[key]
  17. if ok {
  18. l.DoubleList.MoveToFront(v)
  19. return key
  20. }
  21. return -1
  22. }
  23. func (l *LRUCache) Set(key int) {
  24. //查找key,如果已经存在,则把该节点放到链表头
  25. v, ok := l.Hashtable[key]
  26. if ok {
  27. l.DoubleList.MoveToFront(v)
  28. return
  29. }
  30. //如果不存在,判断当前map大小
  31. currLen := len(l.Hashtable)
  32. //map已满,删除链表尾节点,将新节点插入表头
  33. if currLen >= l.Capacity {
  34. del := l.DoubleList.Back().Value
  35. l.DoubleList.Remove(l.DoubleList.Back())
  36. delete(l.Hashtable, del.(int))
  37. }
  38. //map未满,直接插入表头
  39. ins := l.DoubleList.PushFront(key)
  40. l.Hashtable[key] = ins
  41. return
  42. }