0%

缓存算法 LRU 与 LFU

本文主要记录两种常用的缓存算法 LRU 与 LFU 的定义,以及两个算法的基础实现(C#)。

LRU Cache

LRU 全称 Least Recently Used(最近最少使用),当缓存溢出时选择最近最少使用的对象进行淘汰。

LeetCode-中等-146-LRU缓存机制

可以使用双向链表来实现 LRU 机制;

get 方法:

  1. key 不存在则返回 -1;
  2. key 存在则返回对应的 value,并将该节点移动到链表尾部(即该节点置为最近使用的节点)。

put 方法:

  1. key 存在则更新对应的 value,并将该节点从链表中移除,然后添加到链表尾部;
  2. key 不存在则直接将新的键值对添加到链表尾部。

这样一来链表头部的对象就是最近最少使用的对象了,当缓存溢出时,直接移除链表头部的对象即可。

双向链表添加和删除节点的时间复杂度为 O(1),但是查找 key 的时间复杂度是 O(n),为了使 get 方法和 put 方法的时间复杂度达到 O(1),可以使用哈希表辅助进行实现。通过哈希表查找到 key 对应的链表中的节点,然后再对节点进行操作即可。

代码

Pair 类

即键值对。

1
2
3
4
5
6
7
8
9
10
11
12
public class Pair
{
public Pair(int key, int value)
{
Key = key;
Value = value;
}

public int Key { get; }

public int Value { get; set; }
}

LRUCache 类

字段
1
2
3
4
5
6
public class LRUCache
{
private readonly int _capacity; // 容量
private readonly LinkedList<Pair> _cache; // 缓存链表
private readonly Dictionary<int, LinkedListNode<Pair>> _nodeDic; // 节点字典
}
方法
构造方法
1
2
3
4
5
6
public LRUCache(int capacity)
{
_capacity = capacity;
_cache = new LinkedList<Pair>();
_nodeDic = new Dictionary<int, LinkedListNode<Pair>>(capacity);
}
获取元素 Get
1
2
3
4
5
6
7
8
9
10
11
12
13
public int Get(int key)
{
_nodeDic.TryGetValue(key, out var node);
// 不存在 key 则返回 -1
if (node == null) {
return -1;
}

// 存在 key 则更新该节点
_cache.Remove(node);
_cache.AddLast(node);
return node.Value.Value;
}
添加元素 Put
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void Put(int key, int value)
{
_nodeDic.TryGetValue(key, out var node);
// 存在 key
if (node != null) {
// 更新该节点
node.Value.Value = value;
_cache.Remove(node);
_cache.AddLast(node);
}
// 不存在 key
else {
// 容量已满,则移除链表的头节点
if (_cache.Count == _capacity) {
_nodeDic.Remove(_cache.First.Value.Key);
_cache.RemoveFirst();
}
// 将新的节点添加到链表尾部
node = new LinkedListNode<Pair>(new Pair(key, value));
_nodeDic.Add(key, node);
_cache.AddLast(node);
}
}

LFU Cache

LFU 全称 Least Frequently Used(最近最不常使用),当缓存溢出时选择最近最不常使用的对象进行淘汰。

LeetCode-困难-460-LFU缓存

相比于 LRU 算法多了一个条件,即对象的使用频率(frequency);

因此,可以使用多个双向链表来实现,每个链表对应不同的频次,链表及其频率作为键值对存入哈希表中;

当节点被访问时,将节点从旧链表中移除,频率 +1,然后加入对应频率的新链表中。

代码

Pair 类

在键值对的基础上,新增了该对象的使用频率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Pair
{
public Pair(int key, int value)
{
Key = key;
Value = value;
Frequency = 1;
}

public int Key { get; }

public int Value { get; set; }

public int Frequency { get; internal set; }
}

LFUCache 类

字段
1
2
3
4
5
6
7
public class LFUCache
{
private readonly int _capacity; // 容量
private readonly Dictionary<int, LinkedList<Pair>> _cache; // 缓存容器
private readonly Dictionary<int, LinkedListNode<Pair>> _listDic; // 链表字典
private int _minFrequency; // 最小频率
}
方法
构造方法
1
2
3
4
5
6
public LFUCache(int capacity)
{
_capacity = capacity;
_cache = new Dictionary<int, LinkedList<Pair>>(capacity);
_listDic = new Dictionary<int, LinkedListNode<Pair>>(capacity);
}
获取元素 Get
1
2
3
4
5
6
7
8
9
10
11
12
public int Get(int key)
{
_listDic.TryGetValue(key, out var node);
// 不存在 key 则返回 -1
if (node == null) {
return -1;
}

// 存在 key 则更新该节点
VisitNode(node);
return node.Value.Value; // 返回该节点的值
}
添加元素 Put
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void Put(int key, int value)
{
// 容量为 0 则直接返回
if (_capacity == 0) {
return;
}

_listDic.TryGetValue(key, out var node);
// 存在 key
if (node != null) {
// 更新该节点
node.Value.Value = value;
VisitNode(node);
}
// 不存在 key
else {
// 容量已满,则移除频率最小的链表的头节点
if (_listDic.Count == _capacity) {
var list = _cache[_minFrequency];
_listDic.Remove(list.First.Value.Key);
list.RemoveFirst();
}
// 将新的节点添加到频率为 1 的链表的尾部
node = new LinkedListNode<Pair>(new Pair(key, value));
_listDic.Add(key, node);
_cache.TryGetValue(1, out var corList);
if (corList == null) {
corList = new LinkedList<Pair>();
_cache.Add(1, corList);
}
corList.AddLast(node);
_minFrequency = 1;
}
}
访问节点 VisitNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void VisitNode(LinkedListNode<Pair> node)
{
// 从旧链表中移除节点,并更新最小频率
var list = _cache[node.Value.Frequency];
list.Remove(node);
if (list.Count == 0 && node.Value.Frequency == _minFrequency) {
_minFrequency = node.Value.Frequency + 1;
}

// 更新节点的频率,将节点加入新链表
++node.Value.Frequency;
_cache.TryGetValue(node.Value.Frequency, out var corList);
if (corList == null) {
corList = new LinkedList<Pair>();
_cache.Add(node.Value.Frequency, corList);
}
corList.AddLast(node);
}