设计可以变更的缓存结构

设计一种缓存结构,该结构在构造时确定大小,假设大小为 K

  • 功能:

    • set(key,value):将记录(key,value)插入该结构。
    • get(key):返回key对应的value值。
  • 要求:

    • set和get方法的时间复杂度为O(1)。
    • 某个key的set或get操作一旦发生,认为这个 key 的记录成了最经常使用的。
    • 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
  • 举例
    假设缓存结构的实例是cache,大小为3,并依次发生如下行为:
    1,cache.set(“A”,1)。最经常使用的记录为(“A”,1)。
    2,cache.set(“B”,2)。最经常使用的记录为(“B”,2),(“A”,1)变为最不经常的。
    3,cache.set(“C”,3)。最经常使用的记录为(“C”,2),(“A”,1)还是最不经常的。
    4,cache.get(“A”)。最经常使用的记录为(“A”,1),(“B”,2)变为最不经常的。
    5,cache.set(“D”,4)。大小超过了 3,所以移除此时最不经常使用的记录(“B”,2),加入记录(“D”,4),并且为最经常使用的记录,然后(“C”,2)变为最不经常使用的记录。

思路

LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面!
LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!
比如,第二种方法的时期T为10分钟,如果每分钟进行一次调页,主存块为3,若所需页面走向为2 1 2 1 2 3 4
注意,当调页面4时会发生缺页中断

  • 按LRU算法,应换页面1(1页面最久未被使用)
  • 按LFU算法应换页面3(十分钟内,页面3只使用了一次)

区别

  • LRU关键是看页面最后一次被使用到发生调度的时间长短
  • LFU关键是看一定时间段内页面被使用的频率

本题是LRU,使用hashMap和双端队列完成

  • 双端队列的功能

    • 队列头旧尾新,加入新节点从尾部加入
    • 把其中任何一个节点移动到尾部,且不影响其他的联系
    • 可以扔掉第一个,即最旧的,第二个就成为头结点
  • hashMap是完成node与key的相互映射,加速查找

cache的实现

  • set方法
    当有节点进来,进行添加或者修改,然后看窗口值是否达到门限,
    如果没有就添加,冰放入队头,如果达到,就删除队头,放入新节点
    最后把该节点移动到队尾

  • get方法
    从map中得到节点,放入队尾

代码

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
149
// 队列中的结点
public static class Node<V> {
//双向队列中的节点要有前后指针
private Node<V> prior;
private Node<V> next;
private V value;
public Node(V value) {
this.value = value;
this.next = null;
this.prior = null;
}
}
// 双端队列
public static class NodeDoubleLinkedList<V> {
//头是最不经常使用的
private Node<V> head;
//尾是最经常使用的
private Node<V> tail;
public NodeDoubleLinkedList() {
head = null;
tail = null;
}
//新节点添加到尾部
public void addToTail(Node<V> newNode) {
if (newNode == null) {
return;
}
//队列没有节点
if (head == null) {
head = newNode;
tail = newNode;
head.next = null;
head.prior = null;
tail.prior = null;
tail.next = null;
} else {
//有节点
newNode.prior = tail;
tail.next = newNode;
tail = newNode;
tail.next = null;
}
}
//当窗口不够,就需要将头节点去掉
public Node<V> removeHead() {
//没有头结点
if(head == null){
return null;
}
//获取到头结点
Node<V> res = head;
//只有一个节点
if (head == tail) {
head = null;
tail = null;
} else {
head.next.prior = null;
head = head.next;
}
//返回的节点要清理干净,只有值,指针全部清空
res.next = null;
res.prior = null;
return res;
}
//对于刚刚进行操作的节点要移动到队尾
public void moveToTail(Node<V> curNode) {
//移动头结点
if (head == curNode) {
curNode = this.removeHead();
addToTail(curNode);
}else if(curNode != tail){
//如果本身在队尾不用操作
if (head != tail) {
// 删除节点
curNode.prior.next = curNode.next;
curNode.next.prior = curNode.prior;
// 移到尾部
addToTail(curNode);
}
}
}
}
public static class MyCache<K, V> {
//2个hashMap对应key和value,加速查找
private HashMap<K, Node<V>> keyNodeMap;
private HashMap<Node<V>, K> nodeKeyMap;
//双向队列
private NodeDoubleLinkedList<V> nDLList;
//窗口大小
private int windowSize;
//初始化窗口大小
public MyCache(int capacity) {
this.windowSize = capacity;
this.keyNodeMap = new HashMap<K, Node<V>>();
this.nodeKeyMap = new HashMap<Node<V>, K>();
this.nDLList = new NodeDoubleLinkedList<V>();
}
//根据key得到value
public V get(K k) {
if (keyNodeMap.containsKey(k)) {
Node<V> cur = keyNodeMap.get(k);
//最新操作的节点移到队头
nDLList.moveToTail(cur);
return cur.value;
} else {
return null;
}
}
public void set(K k,V value){
if(keyNodeMap.containsKey(k)){
//更新节点
Node<V> cur = keyNodeMap.get(k);
cur.value = value;
//操作过的节点移到队头
nDLList.moveToTail(cur);
}else{
//如果此时窗口达到最大值,则把最久没有用的即头结点去掉
if(keyNodeMap.size() == windowSize){
removeMostUnusedCache();
}
//新增节点
Node<V> newNode= new Node<V>(value);
keyNodeMap.put(k, newNode);
nodeKeyMap.put(newNode, k);
//移到队尾
nDLList.addToTail(newNode);
}
}
public void removeMostUnusedCache() {
//删除头结点
Node<V> rNode = nDLList.removeHead();
K rKey = nodeKeyMap.get(rNode);
//hashMap中删除相关内容
nodeKeyMap.remove(rNode);
keyNodeMap.remove(rKey);
}
}