请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。
在请求分页系统中,只要求将当前需要的一部分页面装入内存,以便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存时,再通过雕爷功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。
页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
常见的页面置换算法有以下几种:
1、最佳置换算法(Optimal,OPT)
2、先进先出页面置换算法(First In First Out,FIFO)
3、最近最久未使用( Least Recently Used,LRU)置换算法
4、最不常用算法(LFU,Least Frequently Used)
5、时钟算法(clock)、改进时钟算法1、最佳置换算法(Optimal,OPT)
目录
2、先进先出页面置换算法(First In First Out,FIFO)
3、最近最久未使用( Least Recently Used,LRU)置换算法
4、最不常用算法(LFU,Least Frequently Used)
1、最佳置换算法(Optimal,OPT)
所选择的被换出的页面将是以后永不使用的,或者是在最长时间内不再被访问,通常可以保证获得最低的缺页率。但是由于人们无法预知一个页面多长时间不再被访问,因此该算法是一种理论上的算法。最佳置换算法可以用来评价其他算法。
2、先进先出页面置换算法(First In First Out,FIFO)
选择换出的页面是最先进入的页面。该算法实现简单,但会将那些经常被访问的页面也被换出,从而使缺页率升高。
3、最近最久未使用( Least Recently Used,LRU)置换算法
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出,它认为过去一段时间内未使用的页面,在最近的将来也不会被访问。
实现方式一:
在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面时最近最久未访问的。
实现方式二:
为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
4、最不常用算法(LFU,Least Frequently Used)
是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。
这种算法选择最近时期使用次数最少的页作为淘汰页。为每个页面配置一个计数器,一旦某页被访问,则将其计数器的值加1,在需要选择一页置换时,则将选择其计数器值最小的页面,即内存中访问次数最少的页面进行淘汰。
这种算法可能存在的问题是:有些也在进程开始时被访问的次数很多,但以后这些页可能不再被访问,这样的页不该长时间停留在内存中。解决这个问题的方法之一是定期的将计时器右移,以形成指数衰减的平均使用次数。
注意LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。
5.抖动
6、LRU实现O(1)实现
题目链接:链接:https://leetcode-cn.com/problems/lru-cache/
已知:
java的数据结构时间复杂度。
栈 Stack
压栈:O(1)
出栈:O(1)
栈顶:O(1)
查找:O(n)
队列 Queue/Deque/Circular Queue
插入:O(1)
移除:O(1)
求长:O(1)
这里可以选择双端队列deque
对于 get 操作,首先判断 key 是否存在:如果 key 不存在,则返回 -1;
如果 key 存在,根据hashmap返回value,并将其提到队首。(双端队列不区分队首队尾,假定addFirst为队首)
对于 put 操作,首先判断 key 是否存在:如果 key 不存在,hashmap ,put进去。双端队列队首插入key。
然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的key;
如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到队首。
public class LRUCache {
private Map<Integer, Integer> cache = new HashMap<Integer, Integer>(); // 用来存放key-value对,cache缓存
private Deque<Integer> deque = new LinkedList<>(); // LRU算法用来淘汰最近最久未使用
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
// 使用伪头部和伪尾部节点
}
public int get(int key) {
if (!cache.containsKey(key)) return -1;
deque.remove(key); // 注意:deque里面remove(Object o) 如果是list,remove有多态要用:remove(Integer.valueOf(key))
deque.addFirst(key);
return cache.get(key); // 如果 key 存在,先通过哈希表定位
}
public void put(int key, int value) {
if (!cache.containsKey(key)) {
cache.put(key, value);
deque.addFirst(key);
if (deque.size() > capacity) {
Integer tmpNode1 = deque.removeLast();
cache.remove(tmpNode1);
}
} else {
cache.put(key, value);
deque.remove(key);
deque.addFirst(key);
}
}
}
对于 get 操作,首先判断 key 是否存在:如果 key 不存在,则返回 -1;
如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
对于 put 操作,首先判断 key 是否存在:如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。
然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
public class LRUCache {
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
if (cache.size() > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
}
} else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}