LRU(Least Recently Used,最近使用最少,最久未使用)是一种缓存淘汰算法。缓存是计算机中用来提高访问资源速度的一种方法。将从数据库、硬盘、网络或其他地方读取到的数据暂存于方便读取的地方(也就是缓存(Cache)),等再次读取时就不必重新在上述地方查找和读取一遍。通常来说,缓存的空间都是有限的,因此只应该保留经常会被读取的数据,而不常被读取的数据则该被淘汰。
LRU是众多缓存淘汰算法中的一种,在缓存容量满了时,淘汰最久没有被访问的元素。他的算法设计很简单。首先是数据结构设计。对于每个缓存中元素,可以用键值对的方式存储,读取时通过键读取对应的数据。缓存是一个优先队列,元素的优先级在运行中会不断改变。每次访问一个键,该键对应元素优先级设为最低(也就是最近访问)。每次插入一个新元素,如果键已存在,则更新其值,并设为最近使用。如果键不存在,则检查容量满了没有。如果满了,则将缓存中优先级最高的元素,也就是最久未访问的元素删除。然后再新增元素,并设为最近访问。
这个是我的伪代码,凑合着看吧
元素 键:值
缓存容量 N
缓存 [缓存元素0,缓存元素1,。。。,缓存元素N]
访问键为x的元素:
如果x在缓存中,则将x对应的元素的值返回,并将该元素设为最近访问
如果不在,则返回空
新增键为y,值为z的元素:
如果缓存中已经存在键为y的元素
则更新其值为z,并设为最近访问
如果缓存中不存在键为y的元素
检查缓存中元素数量是否达容量
如果是则删除最久未访问的元素
新增键为y,值为z的元素到缓存中,并将该元素设为最近访问
具体如何实现这个优先队列,一种简单的实现是,让每个缓存的元素维护一个时间变量。如果元素被访问或是新增,则元素时间置0,其余元素时间+1。当缓存满了需要淘汰内存,则将时间值最大的元素删除。这种设计很明显,时间复杂度很高,每次插入和读取都要更新所有元素的时间,淘汰时,也必须遍历找出时间最大的元素。
更高效的做法是,使用哈希表和双向链表实现。哈希表的特点是查找时间复杂度大部分时候为O(1),但是不存在优先级,甚至不存在先后顺序。而双向链表的特点是,在头尾新增和删除高效,但是查找效率低。
所以,可以设置这样的一个缓存结构。一个哈希表用于储存键和对应元素的地址,用于快速访问和判断元素是否存在。双向链表中,每个节点存储一个元素的键值对。当某个元素被制为最优先,则将对应节点置于链表首。当缓存容量满了,则抛弃链表末尾的节点,并同时将哈希表中的键删除。
为了方便操作,链表采用的双向链表,目的是方便删除。双向链表中的头尾节点是不含有用数据的哑节点。双向链表中节点也要存值的原因是,在抛弃元素时。哈希表可从被抛弃的元素中,很方便地获取对应键。
下面是我的代码
对于双向链表数据结构,我实现了这几个操作,方便被缓存调用。
type LRUDoubleListNode struct {
prev, next *LRUDoubleListNode
key, value int
}
// 双向链表
type LRUDoubleList struct {
head, tail *LRUDoubleListNode
}
// 构造一个初始双向链表
func NewLRUDoubleList() LRUDoubleList {
l := LRUDoubleList{}
l.head = &LRUDoubleListNode{}
l.tail = &LRUDoubleListNode{}
l.head.next = l.tail
l.tail.prev = l.head
return l
}
// 将节点添加到链表首
func (l *LRUDoubleList) addToHead(node *LRUDoubleListNode) {
headNext := l.head.next
l.head.next = node
headNext.prev = node
node.next = headNext
node.prev = l.head
}
// 移除节点
func (l *LRUDoubleList) remove(node *LRUDoubleListNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
// 移除链表尾节点
func (l *LRUDoubleList) removeFromTail() *LRUDoubleListNode {
if l.head.next == l.tail {
return nil
}
node := l.tail.prev
l.remove(node)
return node
}
对于缓存结构,则实现了这些操作
// 缓存结构
type LRUCache struct {
capacity int
keymap map[int]*LRUDoubleListNode
cache LRUDoubleList
}
// 构造一个缓存
func NewLRUCache(capacity int) LRUCache {
lru := LRUCache{capacity: capacity}
lru.keymap = make(map[int]*LRUDoubleListNode)
lru.cache = NewLRUDoubleList()
return lru
}
// 设某个键为最近访问
func (lru *LRUCache) setMostRecently(key int) {
node := lru.keymap[key]
lru.cache.remove(node)
lru.cache.addToHead(node)
}
// 新增元素
func (lru *LRUCache) addItem(key, Value int) {
node := &LRUDoubleListNode{key: key, value: Value}
lru.keymap[key] = node
lru.cache.addToHead(node)
}
// 移除元素
func (lru *LRUCache) removeItem(key int) {
//这个好像用不到
node := lru.keymap[key]
lru.cache.remove(node)
delete(lru.keymap, key)
}
// 移除最久未访问元素
func (lru *LRUCache) removeLeastRecently() {
node := lru.cache.removeFromTail()
delete(lru.keymap, node.key)
}
// 读取操作
func (lru *LRUCache) Get(key int) int {
if item, ok := lru.keymap[key]; ok {
lru.setMostRecently(item.key)
return item.value
} else {
return -1
}
}
// 新增操作
func (lru *LRUCache) Put(key int, value int) {
if item, ok := lru.keymap[key]; ok {
item.value = value
lru.setMostRecently(item.key)
} else {
if len(lru.keymap) >= lru.capacity {
lru.removeLeastRecently()
}
lru.addItem(key, value)
}
}
Python 实现
from typing import Dict, Optional, Union
class DoubleListNode(object):
'''
存储的键值对双向链表节点
Attributes:
key: 缓存的键
value: 缓存的值
prev: 前指针
node: 后指针
'''
def __init__(self, key: Optional[int] = None, value: Optional[int] = None):
self.key: Optional[int] = key
self.value: Optional[int] = value
self.prev: Optional[DoubleListNode] = None
self.next: Optional[DoubleListNode] = None
class DoubleList(object):
'''
存储的键值对双向链表
Attributes:
head: 头指针,不包含有用数据,即哑节点
tail: 尾指针,不包含有用数据,即哑节点
'''
def __init__(self):
self.head = DoubleListNode()
self.tail = DoubleListNode()
self.head.next = self.tail
self.tail.prev = self.head
def add_to_head(self, node: DoubleListNode):
'''
添加新节点到双向节点的头部
Args:
node: 新节点。这个节点应该在调用前创建完成并传入
Returns:
None
Raise:
AttributeError: 找不到头节点的后节点
'''
head_next = self.head.next
if head_next is None:
raise AttributeError("head_next is None")
self.head.next = node
head_next.prev = node
node.next = head_next
node.prev = self.head
def remove(self, node: DoubleListNode):
'''
从链中删除一个节点
Args:
node: 要删除的节点。这个节点应该在调用前通过其他方法找到其实例并传入
Returns:
None
Raise:
AttributeError: 找不到node的前后节点
'''
if node.prev is None or node.next is None:
raise AttributeError("node next or prev is None")
node.prev.next = node.next
node.next.prev = node.prev
def remove_from_tail(self) -> Union[DoubleListNode, None]:
'''
删除链表尾的节点
Args:
None
Returns:
如果链表包含有用数据,则返回被删除的节点实例。否则返回None
Raise:
AttributeError: 找不到tail的前节点
'''
if self.head.next == self.tail:
return None
else:
if self.tail.prev is None:
raise AttributeError("tail prev is None")
node: DoubleListNode = self.tail.prev
self.remove(node)
return node
class LRUCache(object):
'''
LRU 缓存类,
Attributes:
cache: 缓存链表,每个节点存储了该缓存的键值对
capacity: 容量,超过此容量将会抛弃最久未使用的缓存节点
keymap: 哈希表,每个键对应一个缓存节点
'''
def __init__(self, capacity):
'''
初始化,指定容量
'''
self.capacity = capacity
self.keymap: Dict[int, DoubleListNode] = {}
self.cache = DoubleList()
def add_item(self, key: int, value: int):
'''
添加新缓存
Args:
key: 缓存的键
value: 缓存的值
Returns:
None
Raise:
None
'''
node = DoubleListNode(key=key, value=value)
self.keymap[key] = node
self.cache.add_to_head(node)
def remove_item(self, key: int):
'''
删除缓存
Args:
key: 缓存的键
Returns:
None
Raise:
None
'''
node = self.keymap[key]
self.cache.remove(node)
del self.keymap[key]
def remove_least_recently(self):
'''
删除最久未使用缓存
Args:
None
Returns:
None
Raise:
None
'''
node = self.cache.remove_from_tail()
if node is None or node.key is None:
return
del self.keymap[node.key]
def set_most_recently(self, key: int):
'''
设定某个键对应的缓存为最近使用
Args:
key: 缓存的键
Returns:
None
Raise:
None
'''
node = self.keymap[key]
self.cache.remove(node)
self.cache.add_to_head(node)
def get(self, key: int) -> int:
'''
读取缓存
Args:
key: 缓存的键
Returns:
若存在该缓存,则返回该缓存的值,否则,返回-1
Raise:
None
'''
if key in self.keymap:
value = self.keymap[key].value
if value is not None:
self.set_most_recently(key)
return value
return -1
def put(self, key: int, value: int):
'''
新增缓存
Args:
key: 缓存的键
value: 缓存的值
Returns:
None
Raise:
None
'''
if key in self.keymap:
self.keymap[key].value = value
self.set_most_recently(key)
else:
if len(self.keymap) >= self.capacity:
self.remove_least_recently()
self.add_item(key, value)
if __name__ == '__main__':
#for test
lru = LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
lru.get(1)
lru.put(3, 3)
lru.get(2)
lru.put(4, 4)
lru.get(1)
lru.get(3)
lru.get(4)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。