磁盘(硬盘,HDD)是一种机械式存储设备,其 结构图如下:
① 盘片(Platter)
磁性物质
,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面磁盘
(如一个 1T 的机械硬盘)由多个盘片
(如上图中的 0 号盘片)叠加而成(多盘片设计可增加容量)② 磁头(Read/Write Head)
③ 主轴(Spindle)
④ 磁道(Track)
⑤ 扇区(Sector)
如下图:
⑥ 柱面(Cylinder)
磁盘容量 = 磁头数 x 磁道(柱面)数 x 毎道扇区数 x 毎扇区字节数
磁盘通过机械运动和磁性技术实现数据的读写
(1)数据存储方式
(2)数据访问过程
由上,可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。
在“文件的物理结构”小节中,我们经常提到文件数据存放在外存中的几号块(逻辑地址),这个块号就可以转换成(柱面号,盘面号,扇区号)的地址形式。
可根据该地址读取一个“块”,操作如下:
磁盘访问时间由以下三部分组成:
)
优化重点 :寻道时间通常占主导,因此磁盘调度主要针对减少磁头移动距离。
(1) 容量(Capacity)
(2) 转速(RPM, Revolutions Per Minute)
(3) 缓存(Cache)
(4) 平均寻道时间(Average Seek Time)
(5) 数据传输率(Data Transfer Rate)
特性 | HDD(机械硬盘) | SSD(固态硬盘) |
---|---|---|
存储原理 | 磁性存储,机械读写 | 电子存储(NAND 闪存),无机械部件 |
访问速度 | 慢(寻道时间和旋转延迟) | 快(直接电子访问,无延迟) |
耐用性 | 易磨损(机械部件) | 寿命有限(写入次数限制) |
功耗 | 高(电机和磁头运动) | 低 |
价格 | 低(按容量计算) | 高 |
适用场景 | 大容量存储(如备份、冷数据) | 高速读写(如系统盘、数据库) |
原理 :FIFO 算法按照请求到达的顺序进行服务。无论磁头当前位置和请求磁道位置如何,总是先处理最早到达的请求。
示例 :
原理 :SSTF 算法的核心思想是 “就近服务”。它总是选择离当前磁头位置最近的那个磁道请求进行服务。这样做可以最大程度地减少单次寻道时间,从而通常能获得较短的平均寻道时间
示例 : 磁头初始位置:50
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
总寻道距离:12+2+30+23+84+24+4+57 = 236
原理 :SCAN 算法(也称电梯算法)模拟电梯的运行方式。磁头从当前位置开始,沿一个方向(例如,磁道号增大的方向)移动并服务该方向上的所有请求,直到到达磁盘的最远端(或该方向上最后一个请求)。然后,它会反向移动,并服务另一个方向上的所有请求,直到到达另一端(或该方向上最后一个请求)
示例 : 磁头初始位置:50,方向:向右
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
总寻道距离:(12)+(2)+(31)+(24)+(4)+(57)+(180-34=146) + (34-11=23) = 299
原理 : C-SCAN 算法是 SCAN 算法的改进版,旨在提供更均匀的等待时间。磁头从当前位置开始向一个方向(例如,磁道号增大的方向)扫描并服务请求,直到到达磁盘的一段(如: 最大磁道),或者说这个方法上已经没有其他请求为止。然后,它会 立即跳回 到磁盘的另一端 (如:最小磁道),并从那里重新开始向同一方向(磁道号增大的方向)扫描和服务的循环。这个从最大磁道跳回最小磁道的寻道距离会计入总寻道长度,但回程路上不处理任何请求
示例 :
磁头初始位置:50,方向:向右
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
SCAN 和 CSCAN区别:
算法 | 平均寻道时间 | 公平性 | 饥饿问题 | 适用场景 |
---|---|---|---|---|
FCFS | 高 | 高 | 无 | 简单环境 |
SSTF | 最低 | 低 | 有 | 高吞吐量 |
SCAN | 中等 | 中等 | 无 | 普遍适用 |
C-SCAN | 中等 | 高 | 无 | 均匀请求 |
// 磁道范围 -- 用于SCAN/C-SCAN的边界处理
const int MIN_TRACK = 0;
const int MAX_TRACK = 999;
class DiskScheduler {
private:
std::vector<int> _requests; // 存储磁道请求序列
int _currentHeadPos; // 当前磁头存储位置
// 统一输出调度结果
void PrintSchedulingResults(const std::string& algorithmName,
const std::vector<int>& path,
double totalSeekLength,
int numRequests)
{
std::cout << "\n--- " << algorithmName << " 调度结果 ---" << std::endl;
std::cout << "寻道路径: ";
for (int i = 0; i < path.size(); ++i) {
std::cout << path[i];
if (i < path.size() - 1) std::cout << "-> ";
}
std::cout << std::endl;
std::cout << "总寻道长度: " << std::fixed << std::setprecision(2) << totalSeekLength << std::endl;
if (numRequests > 0) {
// 打印两位小数
std::cout << "平均寻道长度: " << std::fixed << std::setprecision(2)
<< totalSeekLength / numRequests << std::endl;
}
else {
std::cout << "没有磁道请求,无法计算平均寻道长度。" << std::endl;
}
std::cout << "--------------------------" << std::endl;
}
bool Empty() {
if (_requests.empty()) {
std::cout << "没有磁道请求需要处理!" << std::endl;
return true;
}
return false;
}
public:
DiskScheduler(const std::vector<int>& requests)
:_requests(requests), _currentHeadPos(0) {}
~DiskScheduler() {
_requests.clear();
_currentHeadPos = 0;
}
// 获取用户输入磁道号, 并进行简单校验 prompt 是输入提醒字符串
static int getValidatedTrackInput(const std::string& prompt) {
int value;
while (true) {
if(!prompt.empty())std::cout << prompt;
std::cin >> value;
if (std::cin.fail() || value < MIN_TRACK || value > MAX_TRACK) {
std::cout << "输入无效!磁道号应在 " << MIN_TRACK << " 到 " << MAX_TRACK
<< " 之间。请重新输入。" << std::endl;
std::cin.clear(); // 清除错误标志
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略错误输入
}
else {
return value;
}
}
}
// 设置当前磁头位置
void SetCurrentHead() {
_currentHeadPos = getValidatedTrackInput("请输入当前所在的磁道号: ");
}
// 显示当前初识磁道请求序列
void DisplayInitialRequests() const {
int max_label_length = 0;
for (size_t i = 1; i <= _requests.size(); ++i) {
int len = std::to_string(i).length() + 1; // "R" + 数字的长度
max_label_length = std::max(max_label_length, len);
}
// 设置固定字段宽度(标签+数值)
int field_width = max_label_length + 3; // 标签+数值总宽度(如 "R1" + 3位数字)
// 打印标签行
for (size_t i = 1; i <= _requests.size(); ++i) {
std::cout << std::setw(field_width) << "R" + std::to_string(i);
}
std::cout << std::endl;
// 打印数值行
for (int request : _requests) {
std::cout << std::setw(field_width) << request;
}
std::cout << "\n----------------------" << std::endl;
}
// 顺序处理
void RunFCFS(bool true){
if (Empty()) return;
// ...
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}
void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}
void RunSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}
void RunCSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}
void RunAll() {
SetCurrentHead();
RunFCFS(false); RunSSTF(false);
RunSCAN(false); RunCSCAN(false);
}
// 显示主菜单
void displayMenu() const {
std::cout << "\n============== 磁盘调度算法模拟 ==============" << std::endl;
std::cout << "1. 先进先出算法(FIFO)" << std::endl;
std::cout << "2. 最短服务时间优先算法(SSTF)" << std::endl;
std::cout << "3. 扫描算法(SCAN)" << std::endl;
std::cout << "4. 循环扫描算法(C-SCAN)" << std::endl;
std::cout << "5. 退出" << std::endl;
std::cout << "==============================================" << std::endl;
std::cout << "请选择: ";
}
};
void RunFCFS(bool flag = true){
if (Empty()) return;
if(flag == true) SetCurrentHead();
std::vector<int> remainingRequests = _requests; //备份
std::vector<int> path; // 路径
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
for(int request: _requests) {
path.emplace_back(request);
totalSeekLength += std::abs(request - currentTrack);
currentTrack = request;
}
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}
代码实现分析:
if (Empty()) return;
: 首先检查请求序列是否为空,如果为空则直接返回。
if(flag == true) SetCurrentHead();
: 如果 flag
为真(默认情况),则提示用户输入当前磁头位置。这使得在单独运行 FIFO 时可以设置磁头位置,而在 RunAll
中统一设置一次即可。
std::vector<int> path; path.push_back(_currentHeadPos);
: path
向量用于记录磁头移动的完整路径,从当前磁头位置开始。
double totalSeekLength = 0; int currentTrack = _currentHeadPos;
: 初始化总寻道长度和当前磁头位置。
for(int request: _requests) { ... }
: 遍历原始的 _requests
向量,因为 FIFO 不改变请求顺序。
path.emplace_back(request);
: 将当前服务的请求磁道添加到寻道路径中。totalSeekLength += std::abs(request - currentTrack);
: 计算从 currentTrack
到 request
的绝对距离,并累加到 totalSeekLength
。currentTrack = request;
: 更新 currentTrack
为刚刚服务的请求磁道,作为下一次寻道计算的起点。PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
: 调用辅助函数打印 FIFO 调度结果。
void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> remainingRequests = _requests; //备份
std::vector<int> path; // 路径
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
while (!remainingRequests.empty()) {
// 最小距离 下个轨道位置
int shortestDistance = -1, nextTrackIndex = -1;
// 找每次剩余磁头中距离当前最近的轨道(绝对距离)
for (size_t i = 0; i < remainingRequests.size(); ++i) {
int dis = std::abs(remainingRequests[i] - currentTrack);
if (nextTrackIndex == -1 || dis < shortestDistance) {
shortestDistance = dis;
nextTrackIndex = i;
}
}
// 此时就是最近, 然后处理走到下一个轨道
totalSeekLength += shortestDistance;
currentTrack = remainingRequests[nextTrackIndex];
path.emplace_back(currentTrack);
remainingRequests.erase(remainingRequests.begin() + nextTrackIndex);
}
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}
代码实现分析:
if (Empty()) return;
/ SetCurrentHead();
: 与 FIFO 类似,进行空检查并设置磁头位置。std::vector<int> remainingRequests = _requests;
: 关键一步。创建原始请求的副本 remainingRequests
。这是因为 SSTF 会修改(删除)已被服务的请求,我们不能直接修改原始 _requests
向量,以确保其他算法能够使用原始数据。path
, totalSeekLength
, currentTrack
: 初始化与 FIFO 类似。while (!remainingRequests.empty()) { ... }
: 循环直到所有请求都被服务。 int shortestDistance = -1, nextTrackIndex = -1;
初始化最短距离和对应请求的索引。for (size_t i = 0; i < remainingRequests.size(); ++i) { ... }
:遍历 remainingRequests
中的每一个请求。int dis = std::abs(remainingRequests[i] - currentTrack);
: 计算当前请求到磁头的距离。if (nextTrackIndex == -1 || dis < shortestDistance) { ... }
: 如果这是第一个请求,或者当前请求的距离比已知的 shortestDistance
更短,则更新 shortestDistance
和 nextTrackIndex
。totalSeekLength += shortestDistance;
: 累加找到的最短距离。currentTrack = remainingRequests[nextTrackIndex];
: 更新磁头位置到刚刚服务的请求磁道。path.emplace_back(currentTrack);
: 将服务过的磁道添加到路径。remainingRequests.erase(remainingRequests.begin() + nextTrackIndex);
: 从 remainingRequests
中移除已被服务的请求。这是 SSTF 区别于 FIFO 的重要操作。PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
: 打印结果。void RunSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> sortedRequests = _requests; // 升序轨道
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector<int> path;
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 使用二分查找当前轨道的大于等于轨道
// 方式一:适用于随机访问容器 (vector)
// int startIndex = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos) - sortedRequests.begin();
// 方式二:适用于所有容器
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
for (int i = startIndex - 1; i >= 0; --i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}
代码实现分析:
if (Empty()) return;
/ SetCurrentHead();
: 检查和设置磁头位置。std::vector<int> sortedRequests = _requests; std::sort(sortedRequests.begin(), sortedRequests.end());
: 关键一步。对请求序列进行升序排序,方便后续按顺序扫描。同样,使用副本避免修改原始数据。path
, totalSeekLength
, currentTrack
: 初始化。auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
: 使用 std::lower_bound
找到在排序后的 sortedRequests
中,第一个大于或等于 _currentHeadPos
的请求的位置。这确定了磁头开始向右扫描的起始点。int startIndex = std::distance(sortedRequests.begin(), it);
: 获取起始点的索引。for (int i = startIndex; i < sortedRequests.size(); ++i) { ... }
: 从 startIndex
开始,遍历到 sortedRequests
的末尾,服务所有大于等于当前磁头位置的请求。totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
:累加寻道距离。currentTrack = sortedRequests[i]; path.emplace_back(currentTrack);
:更新磁头位置和路径。for (int i = startIndex - 1; i >= 0; --i) { ... }
: 从 startIndex - 1
(即当前磁头左侧的第一个请求)开始,反向遍历到 sortedRequests
的开头,服务所有小于当前磁头位置的请求。totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
:累加寻道距离。currentTrack = sortedRequests[i]; path.push_back(currentTrack);
:更新磁头位置和路径。void RunCSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> sortedRequests = _requests; // 升序轨道
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector<int> path;
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 二分
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
// 从最左端开始
for (int i = 0; i < startIndex; ++i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}
代码实现分析:
if (Empty()) return;
/ SetCurrentHead();
: 检查和设置磁头位置。std::vector<int> sortedRequests = _requests; std::sort(sortedRequests.begin(), sortedRequests.end());
: 排序请求。path
, totalSeekLength
, currentTrack
: 初始化。auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
: 找到起始扫描点。for (int i = startIndex; i < sortedRequests.size(); ++i) { ... }
: 从 startIndex
开始,向右服务所有请求。寻道距离累加方式与 SCAN 相同。for (int i = 0; i < startIndex; ++i) { ... }
: 从 sortedRequests
的开头(即最小磁道附近的请求)开始,服务所有在 startIndex
之前的请求。这些请求是在第一次向右扫描时被跳过的。totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
:累加寻道距离。currentTrack = sortedRequests[i]; path.push_back(currentTrack);
:更新磁头位置和路径。PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
: 打印结果。int main() {
std::cout << "欢迎来到磁盘调度算法模拟器!\n";
int numRequests;
while (true) {
std::cout << "请先输入磁道请求数量 (1-" << MAX_TRACK << "): ";
std::cin >> numRequests;
if (std::cin.fail() || numRequests <= 0 || numRequests > MAX_TRACK) {
std::cout << "输入无效!请求数量应为正整数且不超过 " << MAX_TRACK << "。请重新输入。\n";
std::cin.clear();
// 忽略缓冲区中当前行的所有字符(直到遇到换行符),防止无效输入残留在缓冲区中影响后续输入
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
else break;
}
std::vector<int> initialRequests(numRequests);
std::cout << "请输入 " << numRequests << " 个磁道序列 (范围 " << MIN_TRACK << "-" << MAX_TRACK << "):\n";
for (int i = 0; i < numRequests; ++i) {
initialRequests[i] = DiskScheduler::getValidatedTrackInput("请输入第 " + std::to_string(i + 1) + " 个磁道号: ");
// initialRequests[i] = DiskScheduler::getValidatedTrackInput("");
}
DiskScheduler scheduler(initialRequests); // 创建调度器对象
scheduler.DisplayInitialRequests(); // 显示初识请求
while (true) {
scheduler.displayMenu();
int choice;
std::cin >> choice;
if (std::cin.fail()) {
std::cout << "输入无效!请选择一个数字选项。" << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
switch (choice) {
case 1:
scheduler.RunFCFS();
break;
case 2:
scheduler.RunSSTF();
break;
case 3:
scheduler.RunSCAN();
break;
case 4:
scheduler.RunCSCAN();
break;
case 5:
scheduler.RunAll();
break;
case 6:
std::cout << "\n感谢使用,再见!" << std::endl;
return 0;
default:
std::cout << "选择错误,请重新选择 (1-5)!" << std::endl;
break;
}
std::cout << std::endl; // 每次操作后换行,美观
}
return 0;
}
结果如下:
欢迎来到磁盘调度算法模拟器!
请先输入磁道请求数量 (1-999): 8
请输入 8 个磁道序列 (范围 0-999):
请输入第 1 个磁道号: 95
请输入第 2 个磁道号: 180
请输入第 3 个磁道号: 34
请输入第 4 个磁道号: 119
请输入第 5 个磁道号: 11
请输入第 6 个磁道号: 123
请输入第 7 个磁道号: 62
请输入第 8 个磁道号: 64
R1 R2 R3 R4 R5 R6 R7 R8
95 180 34 119 11 123 62 64
----------------------
============== 磁盘调度算法模拟 ==============
1. 先进先出算法(FCFS)
2. 最短服务时间优先算法(SSTF)
3. 扫描算法(SCAN)
4. 循环扫描算法(C-SCAN)
5. 对比所有算法(all)
6. 退出
==============================================
请选择: 5
请输入当前所在的磁道号: 50
--- FIFO 调度结果 ---
寻道路径: 50-> 95-> 180-> 34-> 119-> 11-> 123-> 62-> 64
总寻道长度: 644.00
平均寻道长度: 80.50
--------------------------
--- SSTF 调度结果 ---
寻道路径: 50-> 62-> 64-> 34-> 11-> 95-> 119-> 123-> 180
总寻道长度: 236.00
平均寻道长度: 29.50
--------------------------
--- SCAN 调度结果 ---
寻道路径: 50-> 62-> 64-> 95-> 119-> 123-> 180-> 34-> 11
总寻道长度: 299.00
平均寻道长度: 37.38
--------------------------
--- C-SCAN 调度结果 ---
寻道路径: 50-> 62-> 64-> 95-> 119-> 123-> 180-> 11-> 34
总寻道长度: 322.00
平均寻道长度: 40.25
--------------------------
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <string>
#include <iomanip>
#include <iterator>
// 数据
// 磁道请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
// 当前磁道: 50
// 磁道范围 -- 用于SCAN/C-SCAN的边界处理
const int MIN_TRACK = 0;
const int MAX_TRACK = 999;
class DiskScheduler {
private:
std::vector<int> _requests; // 存储磁道请求序列
int _currentHeadPos; // 当前磁头存储位置
// 统一输出调度结果
void PrintSchedulingResults(const std::string& algorithmName,
const std::vector<int>& path,
double totalSeekLength,
int numRequests)
{
std::cout << "\n--- " << algorithmName << " 调度结果 ---" << std::endl;
std::cout << "寻道路径: ";
for (int i = 0; i < path.size(); ++i) {
std::cout << path[i];
if (i < path.size() - 1) std::cout << "-> ";
}
std::cout << std::endl;
std::cout << "总寻道长度: " << std::fixed << std::setprecision(2) << totalSeekLength << std::endl;
if (numRequests > 0) {
// 打印两位小数
std::cout << "平均寻道长度: " << std::fixed << std::setprecision(2)
<< totalSeekLength / numRequests << std::endl;
}
else {
std::cout << "没有磁道请求,无法计算平均寻道长度。" << std::endl;
}
std::cout << "--------------------------" << std::endl;
}
bool Empty() {
if (_requests.empty()) {
std::cout << "没有磁道请求需要处理!" << std::endl;
return true;
}
return false;
}
public:
DiskScheduler(const std::vector<int>& requests)
:_requests(requests), _currentHeadPos(0) {}
~DiskScheduler() {
_requests.clear();
_currentHeadPos = 0;
}
// 获取用户输入磁道号, 并进行简单校验 prompt 是输入提醒字符串
static int getValidatedTrackInput(const std::string& prompt) {
int value;
while (true) {
if(!prompt.empty())std::cout << prompt;
std::cin >> value;
if (std::cin.fail() || value < MIN_TRACK || value > MAX_TRACK) {
std::cout << "输入无效!磁道号应在 " << MIN_TRACK << " 到 " << MAX_TRACK
<< " 之间。请重新输入。" << std::endl;
std::cin.clear(); // 清除错误标志
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略错误输入
}
else {
return value;
}
}
}
// 设置当前磁头位置
void SetCurrentHead() {
_currentHeadPos = getValidatedTrackInput("请输入当前所在的磁道号: ");
}
// 显示当前初识磁道请求序列
void DisplayInitialRequests() const {
int max_label_length = 0;
for (size_t i = 1; i <= _requests.size(); ++i) {
int len = std::to_string(i).length() + 1; // "R" + 数字的长度
max_label_length = std::max(max_label_length, len);
}
// 设置固定字段宽度(标签+数值)
int field_width = max_label_length + 3; // 标签+数值总宽度(如 "R1" + 3位数字)
// 打印标签行
for (size_t i = 1; i <= _requests.size(); ++i) {
std::cout << std::setw(field_width) << "R" + std::to_string(i);
}
std::cout << std::endl;
// 打印数值行
for (int request : _requests) {
std::cout << std::setw(field_width) << request;
}
std::cout << "\n----------------------" << std::endl;
}
// 顺序处理原则
void RunFCFS(bool flag = true){
if (Empty()) return;
if(flag == true) SetCurrentHead();
std::vector<int> remainingRequests = _requests; //备份
std::vector<int> path; // 路径
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
for(int request: _requests) {
path.emplace_back(request);
totalSeekLength += std::abs(request - currentTrack);
currentTrack = request;
}
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}
// 最近处理原则
void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> remainingRequests = _requests; //备份
std::vector<int> path; // 路径
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
while (!remainingRequests.empty()) {
// 最小距离 下个轨道位置
int shortestDistance = -1, nextTrackIndex = -1;
// 找每次剩余磁头中距离当前最近的轨道(绝对距离)
for (size_t i = 0; i < remainingRequests.size(); ++i) {
int dis = std::abs(remainingRequests[i] - currentTrack);
if (nextTrackIndex == -1 || dis < shortestDistance) {
shortestDistance = dis;
nextTrackIndex = i;
}
}
// 此时就是最近, 然后处理走到下一个轨道
totalSeekLength += shortestDistance;
currentTrack = remainingRequests[nextTrackIndex];
path.emplace_back(currentTrack);
remainingRequests.erase(remainingRequests.begin() + nextTrackIndex);
}
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}
// 基于当前轨道升序走, 走到尽头再回到起始轨道位置降序走
void RunSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> sortedRequests = _requests; // 升序轨道
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector<int> path;
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 使用二分查找当前轨道的大于等于轨道
// 方式一:适用于随机访问容器 (vector)
// int startIndex = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos) - sortedRequests.begin();
// 方式二:适用于所有容器
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
for (int i = startIndex - 1; i >= 0; --i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}
// 和 SCAN 类似, 但是其回头的时候直接从最左端开始
void RunCSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector<int> sortedRequests = _requests; // 升序轨道
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector<int> path;
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 二分
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
// 从最左端开始
for (int i = 0; i < startIndex; ++i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}
void RunAll() {
SetCurrentHead();
RunFCFS(false); RunSSTF(false);
RunSCAN(false); RunCSCAN(false);
}
// 显示主菜单
void displayMenu() const {
std::cout << "\n============== 磁盘调度算法模拟 ==============" << std::endl;
std::cout << "1. 先进先出算法(FCFS)" << std::endl;
std::cout << "2. 最短服务时间优先算法(SSTF)" << std::endl;
std::cout << "3. 扫描算法(SCAN)" << std::endl;
std::cout << "4. 循环扫描算法(C-SCAN)" << std::endl;
std::cout << "5. 对比所有算法(all)" << std::endl;
std::cout << "6. 退出" << std::endl;
std::cout << "==============================================" << std::endl;
std::cout << "请选择: ";
}
};
int main() {
std::cout << "欢迎来到磁盘调度算法模拟器!\n";
int numRequests;
while (true) {
std::cout << "请先输入磁道请求数量 (1-" << MAX_TRACK << "): ";
std::cin >> numRequests;
if (std::cin.fail() || numRequests <= 0 || numRequests > MAX_TRACK) {
std::cout << "输入无效!请求数量应为正整数且不超过 " << MAX_TRACK << "。请重新输入。\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
else break;
}
std::vector<int> initialRequests(numRequests);
std::cout << "请输入 " << numRequests << " 个磁道序列 (范围 " << MIN_TRACK << "-" << MAX_TRACK << "):\n";
for (int i = 0; i < numRequests; ++i) {
initialRequests[i] = DiskScheduler::getValidatedTrackInput("请输入第 " + std::to_string(i + 1) + " 个磁道号: ");
// initialRequests[i] = DiskScheduler::getValidatedTrackInput("");
}
DiskScheduler scheduler(initialRequests); // 创建调度器对象
scheduler.DisplayInitialRequests(); // 显示初识请求
while (true) {
scheduler.displayMenu();
int choice;
std::cin >> choice;
if (std::cin.fail()) {
std::cout << "输入无效!请选择一个数字选项。" << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
switch (choice) {
case 1:
scheduler.RunFCFS();
break;
case 2:
scheduler.RunSSTF();
break;
case 3:
scheduler.RunSCAN();
break;
case 4:
scheduler.RunCSCAN();
break;
case 5:
scheduler.RunAll();
break;
case 6:
std::cout << "\n感谢使用,再见!" << std::endl;
return 0;
default:
std::cout << "选择错误,请重新选择 (1-5)!" << std::endl;
break;
}
std::cout << std::endl; // 每次操作后换行,美观
}
return 0;
}