內存淘汰機制之LRU與LFU
- LRU(Least Recently Used):淘汰 近期最不會訪問的數據
- LFU(Least Frequently Used):淘汰 最不經常使用(訪問次數少)
所謂淘汰就是將內存中指定部分的數據移除,釋放空間提供給新來的數據。
LRU
LeetCode入口👉👉👉No.146
-
存數據,將數據插入鏈表頭部;如果內存滿了,需要先將鏈表尾部數據刪除,再插入
-
取數據,每次將取到的數據重新放到鏈表頭部
LRU一般使用哈希鏈表(哈希表+雙向鏈表)實現,可以在 \(O(1)\) 復雜度內實現插入、刪除。
OrderedDict 的 popitem 方法默認刪除並返回的是字典里的最后一個元素;popitem(last=False) 刪除並返回第一個被添加進去的元素
coding:
#--python
#使用python自帶哈希鏈表(有序字典OrderedDict)實現
from collections import OrderedDict
class LRUCache(OrderedDict):
def __init__(self, capacity: int):
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self:
return -1
self.move_to_end(key)
return self[key]
def put(self, key: int, value: int) -> None:
if key in self:
self.move_to_end(key)
self[key] = value #更新value
if len(self) > self.capacity:
self.popitem(last=False)
自己實現哈希鏈表
#--python
#定義雙向鏈表節點
class DLinkNode():
def __init__(self,key=0,value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache():
def __init__(self,capacity):
self.cache = {}
self.size = 0
self.capacity = capacity
#定義偽頭部 偽尾部節點
self.head,self.tail = DLinkNode(),DLinkNode()
self.head.next = self.tail
self.tail.prev = self.head
#模擬OrderedDict 定義 添加 刪除 移動頭部方法
def _add_node(self,node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove_node(self,node):
node.next.prev = node.prev
node.prev.next = node.next
def _move_to_head(self,node):
self._remove_node(node)
self._add_node(node)
def _pop_tail(self):
node = self.tail.prev
self._remove_node(node)
return node
def get(self,key):
if key not in self.cache:
return -1
node = self.cache[key]
self._move_to_head(node)
return node.value
def put(self,key,value):
#如果key不存在,創建node 添加
if key not in self.cache:
node = DLinkNode(key,value)
self.cache[key] = node
self._add_node(node)
self.size += 1
#如果滿了,刪除雙向鏈表節點 和 字典對應鍵
if self.size > self.capacity:
node = self._pop_tail()
self.cache.pop(node.key)
self.size -= 1
else:
node = self.cache[key]
node.value = value
self._move_to_head(node)
LFU
LeetCode入口👉👉👉No.460
- 維護一個訪問頻次的數據結構,取數據,訪問頻次加一,根據訪問次數排序
- 存數據,當緩存滿時,淘汰點訪問次數最小的
使用雙哈希表 keyMap 和 freqMap
coding:
#--python
#雙哈希表
class Node:
def __init__(self, key, val, pre=None, nex=None, freq=0):
self.pre = pre
self.nex = nex
self.freq = freq
self.val = val
self.key = key
def insert(self, nex):
nex.pre = self
nex.nex = self.nex
self.nex.pre = nex
self.nex = nex
def create_linked_list():
head = Node(0, 0)
tail = Node(0, 0)
head.nex = tail
tail.pre = head
return (head, tail)
class LFUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.size = 0
self.minFreq = 0
self.freqMap = collections.defaultdict(create_linked_list)
self.keyMap = {}
def delete(self, node):
if node.pre:
node.pre.nex = node.nex
node.nex.pre = node.pre
if node.pre is self.freqMap[node.freq][0] and node.nex is self.freqMap[node.freq][-1]:
self.freqMap.pop(node.freq)
return node.key
def increase(self, node):
node.freq += 1
self.delete(node)
self.freqMap[node.freq][-1].pre.insert(node)
if node.freq == 1:
self.minFreq = 1
elif self.minFreq == node.freq - 1:
head, tail = self.freqMap[node.freq - 1]
if head.nex is tail:
self.minFreq = node.freq
def get(self, key: int) -> int:
if key in self.keyMap:
self.increase(self.keyMap[key])
return self.keyMap[key].val
return -1
def put(self, key: int, value: int) -> None:
if self.capacity != 0:
if key in self.keyMap:
node = self.keyMap[key]
node.val = value
else:
node = Node(key, value)
self.keyMap[key] = node
self.size += 1
if self.size > self.capacity:
self.size -= 1
deleted = self.delete(self.freqMap[self.minFreq][0].nex)
self.keyMap.pop(deleted)
self.increase(node)