题目:页面置换
背景介绍:分页内存管理
在现代操作系统中,为了高效管理内存,系统会将进程的虚拟内存划分为大小相等的页 (Page) 。物理内存同样被划分为大小相同的页框 (Frame) 。当进程需要访问某个页的数据时,操作系统必须将该页从磁盘调入物理内存的某个页框中,这个过程我们称之为缓存。
你的任务是模拟这个缓存和置换过程,计算在给定的页面访问序列下,总共发生了多少次页置换(即因缓存已满而需要替换掉一个旧页面的情况)。
核心概念
-
页框 (Frames): 物理内存的容量,由
frameNum决定,表示最多可以同时缓存多少个页。 -
缓存中的页: 每个被缓存的页都需要记录两个关键属性:
- 访问次数 (Access Count): 该页被访问的总次数。
- 访问时间 (Access Time): 该页最近一次被访问的时间点。我们可以用一个从 0 开始递增的计时器来表示。
-
页面访问: 按顺序处理
pages数组中的每一个页面访问请求。
功能要求:页面访问规则
对于每一个页面访问请求,遵循以下规则:
情况一:页面命中 (Page Hit)
-
当要访问的页已存在于缓存中:
- 不发生置换。
- 该页的访问次数加 1。
- 更新该页的访问时间为当前最新时间。
情况二:页面缺失 (Page Fault / Miss)
-
当要访问的页不存在于缓存中:
-
a. 如果有空闲页框:
- 不发生置换。
- 将新页放入一个空闲页框中。
- 初始化该页的访问次数为 1,访问时间为当前最新时间。
-
b. 如果没有空闲页框:
- 发生一次页置换,这是我们需要计数的事件。
- 必须先按照下面的置换策略从缓存中选择一个“牺牲页”并将其移除。
- 然后将新页放入腾出的页框中,并初始化其访问次数为 1,访问时间为当前最新时间。
-
置换策略详解
当缓存已满,需要置换一个页面时,遵循一个两步决策过程:
第一步:确定候选范围
- 将缓存中所有已缓存的页,按照访问时间从早到晚排序。
- 选择其中最老的
windowSize个页,作为“过期候选页”。
第二步:选择牺牲页
-
在“过期候选页”的范围内,应用以下规则来确定最终要被置换出去的牺牲页:
- 优先规则: 优先选择访问次数最少的那个页。
- 平局规则: 如果有多个页的访问次数都同为最少,则在它们当中选择访问时间最老(即最久未被访问)的那个页。
输入格式
-
第一个参数
frameNum:- 物理内存的页框数量。
1 <= frameNum <= 64
-
第二个参数
windowSize:- 置换策略中候选范围的大小。
1 <= windowSize <= frameNum
-
第三个参数
pages:- 一个整数数组,代表按顺序发生的页面访问请求。
0 <= pages[i] <= 100(页编号)pages.length < 2000
输出格式
- 一个整数,表示在处理完所有页面访问请求后,总共发生了多少次页置换。
样例
输入样例 1
3 2 [51, 52, 53, 54, 54, 51, 52, 53, 54]
输出样例 1
4
样例 1 执行流程详解
- 系统状态:
frameNum=3,windowSize=2 - 时间
t从 0 开始,每次访问递增。
| 访问页 | 操作类型 | 缓存状态 [页号(访问次数, 访问时间)] | 置换次数 | 解释 |
|---|---|---|---|---|
| 51 | Miss (有空闲) | [51(1, t=0)] | 0 | 缓存未满,直接放入。 |
| 52 | Miss (有空闲) | [51(1,0), 52(1,1)] | 0 | 缓存未满,直接放入。 |
| 53 | Miss (有空闲) | [51(1,0), 52(1,1), 53(1,2)] | 0 | 缓存填满。 |
| 54 | Miss (需置换) | [52(1,1), 53(1,2), 54(1,3)] | 1 | 置换开始: 1. 按访问时间排序: 51(t=0), 52(t=1), 53(t=2)2. 候选范围 ( windowSize=2): {51, 52}3. 候选者访问次数都是1,平局。选择时间最老的 51 进行置换。 |
| 54 | Hit | [52(1,1), 53(1,2), 54(2,4)] | 1 | 54 已在缓存中。访问次数+1,更新访问时间。 |
| 51 | Miss (需置换) | [53(1,2), 54(2,4), 51(1,5)] | 2 | 置换开始: 1. 按访问时间排序: 52(t=1), 53(t=2), 54(t=4)2. 候选范围: {52, 53}3. 候选者访问次数都是1,平局。选择时间最老的 52 进行置换。 |
| 52 | Miss (需置换) | [54(2,4), 51(1,5), 52(1,6)] | 3 | 置换开始: 1. 按访问时间排序: 53(t=2), 54(t=4), 51(t=5)2. 候选范围: {53, 54}3. 候选者 53 (访问1次) vs 54 (访问2次)。选择访问次数少的 53 进行置换。 |
| 53 | Miss (需置换) | [54(2,4), 52(1,6), 53(1,7)] | 4 | 置换开始: 1. 按访问时间排序: 54(t=4), 51(t=5), 52(t=6)2. 候选范围: {54, 51}3. 候选者 54 (访问2次) vs 51 (访问1次)。选择访问次数少的 51 进行置换。 |
| 54 | Hit | [52(1,6), 53(1,7), 54(3,8)] | 4 | 54 已在缓存中。访问次数+1,更新访问时间。 |
最终,总置换次数为 4。
import java.util.*;
public class PageReplacementSimulator {
/**
* 内部类,用于封装缓存在页框中的页信息
*/
static class PageEntry {
int pageNumber;
int accessCount;
int accessTime;
public PageEntry(int pageNumber, int accessCount, int accessTime) {
this.pageNumber = pageNumber;
this.accessCount = accessCount;
this.accessTime = accessTime;
}
}
/**
* 计算页置换次数
*
* @param frameNum 物理内存的页框数量
* @param windowSize 置换时过期候选窗口的大小
* @param pages 依次访问的页编号数组
* @return 发生页置换的总次数
*/
public int countPageReplacements(int frameNum, int windowSize, int[] pages) {
// 使用 Map 模拟物理内存,Key: pageNumber, Value: PageEntry
Map<Integer, PageEntry> frames = new HashMap<>();
int replacementCount = 0;
int currentTime = 0;
for (int pageToAccess : pages) {
currentTime++; // 每次访问,时间戳递增
// --- 1. 检查页是否命中缓存 ---
if (frames.containsKey(pageToAccess)) {
PageEntry entry = frames.get(pageToAccess);
entry.accessCount++;
entry.accessTime = currentTime;
continue; // 命中,继续下一次访问
}
// --- 2. 缺页处理 ---
// 2A. 如果有空闲页框
if (frames.size() < frameNum) {
frames.put(pageToAccess, new PageEntry(pageToAccess, 1, currentTime));
}
// 2B. 如果没有空闲页框,需要置换
else {
replacementCount++; // 发生了一次置换
// --- 执行置换策略 ---
// 步骤 1: 将所有缓存页按访问时间排序,选出候选者
List<PageEntry> allCachedPages = new ArrayList<>(frames.values());
allCachedPages.sort(Comparator.comparingInt(p -> p.accessTime));
// 获取窗口内的候选页
List<PageEntry> candidates = new ArrayList<>();
for (int i = 0; i < windowSize && i < allCachedPages.size(); i++) {
candidates.add(allCachedPages.get(i));
}
// 步骤 2: 在候选页中选择被置换页
// 优先按访问次数升序,其次按访问时间升序
PageEntry victim = Collections.min(candidates,
Comparator.comparingInt((PageEntry p) -> p.accessCount)
.thenComparingInt(p -> p.accessTime)
);
// 执行置换
frames.remove(victim.pageNumber);
frames.put(pageToAccess, new PageEntry(pageToAccess, 1, currentTime));
}
}
return replacementCount;
}
public static void main(String[] args) {
PageReplacementSimulator simulator = new PageReplacementSimulator();
// 样例 1
int frameNum1 = 3;
int windowSize1 = 2;
int[] pages1 = {51, 52, 53, 54, 54, 51, 52, 53, 54};
int result1 = simulator.countPageReplacements(frameNum1, windowSize1, pages1);
System.out.println("样例 1 的置换次数: " + result1); // 预期输出: 4
// 样例 2
int frameNum2 = 4;
int windowSize2 = 3;
int[] pages2 = {1, 1, 30, 20, 5, 6, 5, 20, 5, 20, 1, 6, 7, 6};
int result2 = simulator.countPageReplacements(frameNum2, windowSize2, pages2);
System.out.println("样例 2 的置换次数: " + result2); // 预期输出: 2
}
}