LRU和LFU
# LRU和LFU的区别
LRU和LFU都是内存管理的页面置换算法。
LRU:最近最少使用(最长时间)淘汰算法(Least Recently Used)。LRU是淘汰最长时间没有被使用的页面。
LFU:最不经常使用(最少次)淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的页面。
例子
假设LFU方法的时期T为10分钟,访问如下页面所花的时间正好为10分钟,内存块大小为3。若所需页面顺序依次如下:
2 1 2 1 2 3 4
---------------------------------------->
- 当需要使用页面4时,内存块中存储着1、2、3,内存块中没有页面4,就会发生缺页中断,而且此时内存块已满,需要进行页面置换。
- 若按LRU算法,应替换掉页面1。因为页面1是最长时间没有被使用的了,页面2和3都在它后面被使用过。
- 若按LFU算法,应换页面3。因为在这段时间内,页面1被访问了2次,页面2被访问了3次,而页面3只被访问了1次,一段时间内被访问的次数最少。
LRU 关键是看页面最后一次被使用到发生替换的时间长短,时间越长,页面就会被置换;
LFU关键是看一定时间段内页面被使用的频率(次数),使用频率越低,页面就会被置换。
LRU算法适合:较大的文件比如游戏客户端(最近加载的地图文件);
LFU算法适合:较小的文件和零碎的文件比如系统文件、应用程序文件 ;
LRU消耗CPU资源较少,LFU消耗CPU资源较多。
# LRU (最长时间)
最近最久未使用算法, LRU是淘汰最长时间没有被使用的页面
# 功能
- 缓存容量capacity为正整数,缓存的key、value均为int类型
- 读缓存
func get(key int) int
:- key已存在,返回对应value
- key不存在,返回-1
- 写缓存func put(key int, value int):
- key已存在,修改对应value
- key不存在,写入该组缓存,若写入前缓存容量已达上限,则应淘汰最久未使用的缓存(强调:读、写缓存均视为使用)
# 数据结构
使用双向链表维护缓存的上一次使用时间:
- 约定:链表正方向(从头部到尾部)节点按照使用时间排序——越早使用(即久未使用)的节点,越靠近链表尾部
- 维护:每使用一次缓存,就将该缓存对应的链表节点移动到链表头部;缓存淘汰时,只需要删除尾部节点即可
增加一个map,记录
key
到链表节点的映射关系; 解决如果只使用双向链表,每次判断key
是否存在时,都要遍历链表
- cache:
map[int]*listNode
,key
到节点的映射; 其中 listNode data:key
,value
- list:
*listNode
,双向链表,维护缓存的上一次使用时间 - capacity:
int
,链表容量
# 伪代码
- 读缓存
- key存在:
- 在原链表中删除该缓存节点,重新插入到链表头部,
- 返回对应的value
- key不存在:
- 返回-1
- key存在:
- 写缓存(更新缓存)
- Key存在:
- 更新缓存节点的value值
- 在原链表中删除该缓存节点,并把该重新插入到链表头部
- Key不存在:
- 容量已达上限:
- 在链表中删除尾部节点(记录该节点的key)
- 根据上一步中记录的key,删除对应的映射关系
- 根据输入参数构造新的节点:
- 将新的节点插入链表头部
- 新增key到新的节点的映射关系
- 容量未达上限:
- 根据输入参数构造新的节点:
- 将新的节点插入链表头部
- 新增key到新的节点的映射关系
- 容量已达上限:
- Key存在:
# Golang代码实现
// 双向链表节点
type doublyListNode struct {
key int
value int
prev *doublyListNode
next *doublyListNode
}
// 构造一个双向空链表(首尾几点都是空节点)
func newDoublyList() *doublyListNode {
headNode := &doublyListNode{}
tailNode := &doublyListNode{}
headNode.next = tailNode
tailNode.prev = headNode
return headNode
}
// 把节点添加到链表头部
func (dl *doublyListNode) addToHead(node *doublyListNode) {
dl.next.prev = node
node.next = dl.next
dl.next = node
node.prev = dl
}
// 删除链表中的节点
func removeNode(node *doublyListNode) {
node.next.prev = node.prev
node.prev.next = node.next
}
// LRUCache 具体的缓存
type LRUCache struct {
cache map[int]*doublyListNode
head *doublyListNode
tail *doublyListNode
capacity int
}
// Constructor 构建缓存容器
func Constructor(capacity int) LRUCache {
dl := newDoublyList()
return LRUCache{
cache: make(map[int]*doublyListNode),
head: dl,
tail: dl.next,
capacity: capacity,
}
}
func (lruCache *LRUCache) Get(key int) int {
// 根据key 获取缓存
v, ok := lruCache.cache[key]
// 如果没有缓存, 返回-1
if !ok {
return -1
}
// 如果有缓存
removeNode(v) // 移除该缓存
lruCache.head.addToHead(v) // 把缓存添加双向链表头部
return v.value
}
// Put 新建缓存
func (lruCache *LRUCache) Put(key int, value int) {
// 已经有缓存
if v, ok := lruCache.cache[key]; ok { // v 是双链表中的节点
v.value = value // 更新链表节点中的值
lruCache.cache[key] = v // 更新缓存中映射关系
removeNode(v) // 移除该缓存
lruCache.head.addToHead(v) // 把缓存添加双向链表头部
return
}
// 缓存超长 淘汰缓存
if len(lruCache.cache) >= lruCache.capacity {
node := lruCache.tail.prev
removeNode(node) // 删除该节点
delete(lruCache.cache, node.key) // 清除 最近最少使用的缓存
}
newNode := &doublyListNode{
key: key,
value: value,
}
lruCache.cache[key] = newNode
lruCache.head.addToHead(newNode)
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# LFU (最少次)
# 功能
- 缓存容量capacity、缓存的key和value均为自然数(可以为0,代码中单独处理)
- 读缓存func get(key int) int:(与lru相同)
- key已存在,返回对应value
- key不存在,返回-1
- 写缓存func put(key int, value int):
- key已存在,修改对应value
- key不存在,写入该组缓存,若写入前缓存容量已达上限,则应淘汰使用次数最少的缓存(记其使用次数为n);
- 若使用次数为n的缓存数大于一个,则淘汰最久未使用的缓存(即,此时遵守lru规则)
# 数据结构
// LFUCache 具体的缓存 frequency 是使用次数
type LFUCache struct {
recent map[int]*doublyListNode // frequency 到使用次数为 frequency 的节点中,最近使用的一个的映射
count map[int]int // frequency 到对应频率的节点数量的映射
cache map[int]*doublyListNode // key到节点的映射
list *doublyList // 双向链表,维护缓存的使用次数(优先)和上一次使用时间
capacity int // 容量
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 伪代码
- 读缓存
- 存在:(记节点frequency为n)
- 若存在其他frequency = n+1的节点,则将节点移动到所有frequency = n+1的节点的前面;
- 否则,若存在其他frequency = n的节点,且当前节点不是最近节点,则将节点移动到所有frequency = n的节点的前面;
- 否则,不移动节点(该情况下,节点就应该呆在它现在的位置)
- 更新recent
- 更新count
- 将节点frequency +1
- 返回节点的value
- 不存在:返回-1
- 存在:(记节点frequency为n)
- 写缓存
- key存在
- 参考读缓存——key存在,额外修改对应的value即可
- 不存在:
- 若当前缓存容量已达上限:
- 淘汰尾部的缓存节点(记节点freq为n)
- 若不存在其他freq = n的节点,则将recent置空
- 更新cache
- 更新count
- 构造新节点:key,value,frequency = 1
- 是否存在其他frequency = 1的节点:
- 存在:插入到它们的前面
- 不存在:插入链表尾部
- 更新recent
- 更新cache
- 更新count
- 若当前缓存容量已达上限:
- key存在
# Golang代码实现
// 双向链表
type doublyList struct {
head *doublyListNode
tail *doublyListNode
}
// 删除尾结点
func (dl *doublyList) removeTail() {
pre := dl.tail.prev.prev
pre.next = dl.tail
dl.tail.prev = pre
}
// 链表是否为空
func (dl *doublyList) isEmpty() bool {
return dl.head.next == dl.tail
}
// 双向链表节点
type doublyListNode struct {
key int
value int
frequency int // 使用次数
prev *doublyListNode
next *doublyListNode
}
// 在某一个节点之前插入一个节点
func addBefore(currNode *doublyListNode, newNode *doublyListNode) {
pre := currNode.prev
pre.next = newNode
newNode.next = currNode
currNode.prev = newNode
newNode.prev = pre
}
// LFUCache 具体的缓存
type LFUCache struct {
recent map[int]*doublyListNode // frequency 到使用次数为 frequency 的节点中,最近使用的一个的映射
count map[int]int // frequency 到对应频率的节点数量的映射
cache map[int]*doublyListNode // key到节点的映射
list *doublyList // 双向链表,维护缓存的使用次数(优先)和上一次使用时间
capacity int // 容量
}
func removeNode(node *doublyListNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
// Constructor 构建缓存容器
func Constructor(capacity int) LFUCache {
return LFUCache{
recent: make(map[int]*doublyListNode),
count: make(map[int]int),
cache: make(map[int]*doublyListNode),
list: newDoublyList(),
capacity: capacity,
}
}
func newDoublyList() *doublyList {
headNode := &doublyListNode{}
tailNode := &doublyListNode{}
headNode.next = tailNode
tailNode.prev = headNode
return &doublyList{
head: headNode,
tail: tailNode,
}
}
func (lfu *LFUCache) Get(key int) int {
if lfu.capacity == 0 {
return -1
}
node, ok := lfu.cache[key]
if !ok { // key不存在
return -1
}
// key已存在
next := node.next
if lfu.count[node.frequency+1] > 0 {
// 存在其他使用次数为n+1的缓存,将指定缓存移动到所有使用次数为n+1的节点之前
removeNode(node)
addBefore(lfu.recent[node.frequency+1], node)
} else if lfu.count[node.frequency] > 1 && lfu.recent[node.frequency] != node {
// 不存在其他使用次数为n+1的缓存,但存在其他使用次数为n的缓存,且当前节点不是最近的节点
// 将指定缓存移动到所有使用次数为n的节点之前
removeNode(node)
addBefore(lfu.recent[node.frequency], node)
}
// 更新recent
lfu.recent[node.frequency+1] = node
if lfu.count[node.frequency] <= 1 { // 不存在其他freq = n的节点,recent置空
lfu.recent[node.frequency] = nil
} else if lfu.recent[node.frequency] == node { // 存在其他freq = n的节点,且recent = node,将recent向后移动一位
lfu.recent[node.frequency] = next
}
// 更新使用次数对应的节点数
lfu.count[node.frequency+1]++
lfu.count[node.frequency]--
// 更新缓存使用次数
node.frequency++
return node.value
}
// Put 新建缓存
func (lfu *LFUCache) Put(key int, value int) {
if lfu.capacity == 0 {
return
}
node, ok := lfu.cache[key]
if ok { // key已存在
lfu.Get(key)
node.value = value
return
}
// key不存在
if len(lfu.cache) >= lfu.capacity { // 缓存已满,删除最后一个节点,相应更新cache、count、recent(条件)
tailNode := lfu.list.tail.prev
lfu.list.removeTail()
if lfu.count[tailNode.frequency] <= 1 {
lfu.recent[tailNode.frequency] = nil
}
lfu.count[tailNode.frequency]--
delete(lfu.cache, tailNode.key)
}
newNode := &doublyListNode{
key: key,
value: value,
frequency: 1,
}
// 插入新的缓存节点
if lfu.count[1] > 0 {
addBefore(lfu.recent[1], newNode)
} else {
addBefore(lfu.list.tail, newNode)
}
// 更新recent、count、cache
lfu.recent[1] = newNode
lfu.count[1]++
lfu.cache[key] = newNode
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
上次更新: 2023/08/27, 21:33:49