LRU, 2022-10-17

(2022.10.17 Mon)
LRU, Least Recently Used是一种页面置换算法(page replacement algorithm),其应用包括Python内存管理和智能手机"最近任务"等场景。LRU将刚刚使用的任务提前,选择最久未使用的任务予以淘汰。

比如手机的最近任务浏览功能,你可以在这里看到按时间顺序排列的最近使用的功能,每次当点击某个app,其对应的图标就会出现在最近任务列表的第一位,其他已经保存的任务依次向后移动。


LRU_case.jpeg

LRU的实现

在了解了LRU的基本特征之后,这部分我们实现LRU。根据其特点描述,每个app/任务会在使用之后放置于整个LRU队列的最前端,其他任务向后移动。该部分可考虑使用队列(array)或链表(linked list)实现。

使用array实现,优势在于占用空间小且连续(考虑到链表的每个元素需要保存指向其他元素的指针),访问元素只需要通过index即可实现,复杂度为常数时间O(1);劣势在于当某任务被前置时,排名其后的所有元素都要向后移动,复杂度为O(n)

使用linked list实现,优势在于其中元素的位置调整灵活,只需要调整前后指针,复杂度为O(1);劣势在于占用空间相比array更多,比如每个元素需要包含指向其他元素的指针,访问元素需要从头开始遍历,最坏情况达到了O(n)

然而在Linked list的实现中,可通过hash table的方式,将访问元素的复杂度从O(n)降为O(1)

实现:Doubly Linked List

在双向链表中,每个元素保留向前和向后两个指针,为了hash table使用,也保留了key和element/value两个字段的信息。

该Python实现中提供了三个类,NodedoublyLinkedListLRU,其中前两个是LRU类实现的基础。这里的hash table实现采用了Python的dict。

  • Node类:代表了每个任务/app,包含4个字段,keyvalueprevious pointernext pointer
  • doublyLinkedList类:双向链表,可实现删除点、尾部弹出、从头加入节点。
  • LRU类:包含两个方法,setget,前者为加入新的任务/app,在加入该新的对象时,自动将其放在链表的头部,后者为访问特定任务/app,访问成功后将该对象置于链表的头部。
class Node:
    """
    Node object, including 4 fields, i.e., key, value, previous&next pointer
    """
    def __init__(self, key, value, prev=None, nex=None):
        self._key = key
        self._value = value
        self._prev = prev
        self._next = nex

class doublyLinkedList(object):
    def __init__(self):
        """
        head and tail work as placeholder
        """
        self._head = Node(None, None)
        self._tail = Node(None, None)
        self._len = 0

    def append_front(self, node):
        """
        add a node to the front
        """
        if self._len <= 0:
            logging.info(f"""into append_front method: {node}""")
            node._prev = self._head
            node._next = self._tail
            self._head._next = node
            self._tail._prev = node
            self._len += 1
            return
        old_head = self._head._next
        self._head._next = node
        node._prev = self._head
        node._next = old_head
        if old_head:
            old_head._prev = node
        self._len += 1

    def remove(self, node):
        """
        remove a node, update the pointers of prev&next nodes
        """
        logging.info(f"""into remove method of DLL: {node}""")
        logging.info(f"""prev pointer: {node._prev}""")
        prev = node._prev
        logging.info(f"""next pointer: {node._next}""")
        nex = node._next
        if node == self._head._next:
            self._head._next = nex
        if node == self._tail._prev:
            self._tail._prev = prev
        if prev:
            prev._next = nex
        if nex:
            nex._prev = prev
    
        node._prev = None
        node._next = None
        self._len -= 1
        return node

    def pop_tail(self):
        """
        remove last node in the list
        """
        tail = self._tail._prev
        if not tail:
            return tail
        prev_tail = tail._prev
        self._tail = prev_tail
        self._len -= 1
        if prev_tail:
            prev_tail._next = self._tail
        return tail

class LRU():
    """
    LRU is based on doubly linked list, setup capacity though
    """
    def __init__(self, capacity=5):
        self._capacity = capacity
        self._keys = {}
        self._list = doublyLinkedList()

    def set(self, key, value):
        """
        add a new node/element
        """
        if key in self._keys:
            logging.info(f"""key exists in the hash table: {key}""")
            node = self._keys[key]
            node._value = value
            logging.info(f"""about to conduct remove and append_front operation: {key}""")
            logging.info(f"""remove first: {key}""")
            r_node = self._list.remove(node)
            logging.info(f"""append front follows: {key}""")
            self._list.append_front(r_node)
            print(self._list._len, self._list)
            return f"""{key} updated: {value}"""
        if self._list._len >= self._capacity:
            p_node = self._list.pop_tail()
            if p_node:
                del self._keys[p_node._key]
                del p_node
        logging.info(f"""about to add new key to the hash map: {key}""")
        node = Node(key, value)
        self._keys[key] = node
        logging.info(f"""conduct append_front operation: {key}""")
        self._list.append_front(node)
        print("SET - key: %s, value: %s" % (key, value))
        print("length: ", self._list._len, ", head:", self._list._head._next._value)

    def get(self, key):
        """
        access to an existing node in the list
        """
        if key not in self._keys:
            return None
        logging.info(f"""get method: key {key} in the list""")
        node = self._keys[key]
        logging.info(f"""get method: conduct remove and append_front operations""")
        r_node = self._list.remove(node)
        self._list.append_front(r_node)
        print("GET - key: %s, value: %s" % (key, node._value))
        return node._value

运行 查看过程

if __name__ == '__main__':
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO)
    lru = LRU(3)
    lru.set('name', 'jeff')
    lru.set('gender', 'male')
    lru.set('age', 39)
    lru.set('location', 'Hong Kong')
    lru.get('age')
    # lru.set
    print('lru keys: ', lru._keys)
    print('lru list: ', lru._list)

实现:Redis

Redis提供了LRU的实现方案。
placeholder

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容