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)

rwxe
91 声望5 粉丝

no tengo trabajo.