lru算法底层原理 面试思路

56 阅读2分钟

LRU(Least Recently Used,最近最少使用)是一种常见的缓存淘汰策略,核心思想是当缓存满时,优先淘汰最近最少使用的元素。

get先检查哈希表是否存在key,存在去链表中获取,更新key到链表头部,同时更新hash表key的下标 put时候,同样操作,只是对于不存在的key放在链表尾部,同时检查容量是否满了,进行删除尾部key

在 Go 中实现 LRU 缓存,通常可以结合哈希表( map )和双向链表来实现,哈希表用于快速查找,双向链表用于维护元素的访问顺序。以下是一个简单实现:

package main

import "container/list"

// LRUCache 表示LRU缓存
type LRUCache struct {
   capacity int                   // 缓存容量
   cache    map[int]*list.Element  // 哈希表,键为缓存键,值为链表节点
   list     *list.List             // 双向链表,用于维护访问顺序,节点值为键值对
}

// entry 表示链表节点中存储的键值对
type entry struct {
   key   int
   value int
}

// NewLRUCache 初始化一个LRU缓存
func NewLRUCache(capacity int) *LRUCache {
   return &LRUCache{
       capacity: capacity,
       cache:    make(map[int]*list.Element),
       list:     list.New(),
   }
}

// Get 获取缓存中的值
func (c *LRUCache) Get(key int) int {
   // 如果键存在
   if elem, ok := c.cache[key]; ok {
       // 将节点移到链表头部(表示最近使用)
       c.list.MoveToFront(elem)
       return elem.Value.(*entry).value
   }
   return -1 // 键不存在时返回-1
}

// Put 向缓存中添加或更新键值对
func (c *LRUCache) Put(key int, value int) {
   // 如果键已存在
   if elem, ok := c.cache[key]; ok {
       // 更新值
       elem.Value.(*entry).value = value
       // 将节点移到链表头部
       c.list.MoveToFront(elem)
       return
   }

   // 键不存在,检查缓存是否已满
   if c.list.Len() == c.capacity {
       // 缓存满,删除链表尾部节点(最近最少使用)
       tail := c.list.Back()
       if tail != nil {
           delete(c.cache, tail.Value.(*entry).key)
           c.list.Remove(tail)
       }
   }

   // 添加新节点到链表头部
   elem := c.list.PushFront(&entry{key: key, value: value})
   c.cache[key] = elem
}
 

代码说明:

  • 核心结构  LRUCache  包含缓存容量、哈希表和双向链表。
  • 双向链表  list  用于记录元素的访问顺序,最近使用的元素放在头部,最少使用的在尾部。
  • 哈希表  cache  用于快速根据键找到对应的链表节点,时间复杂度为 O(1)。
  •  Get  方法:若键存在,将对应节点移到链表头部(标记为最近使用)并返回值;否则返回 -1。
  •  Put  方法:若键存在则更新值并移到头部;若不存在,先检查容量,满则删除尾部节点,再添加新节点到头部。

这个实现的  Get  和  Put  操作时间复杂度均为 O(1),符合 LRU 缓存的高效性要求。