实验7 文件结构
一、实验内容
1.把文件的逻辑结构转换成存储结构。
2.设计便于顺序存取和直接存取的文件存储结构。
二、实验目的
1.研究用户概念中的信息组织方式。
2.理解文件的逻辑结构、存取结构、存取方式之间的联系。
3.模拟设计文件的存储结构。
三、实验原理
1.第一题:模拟设计MS-DOS操作系统中磁盘文件的存储结构。
【提示】
⑴当用户对记录式文件采用顺序存储方式时,用户总是依次地访问一个个逻辑记录,即当访问了第i个记录后,下次总是访问第i+1个记录。所以,当用户采用顺序存取方式访问文件时,只要给出访问要求(读或写)而无需再指出要访问的记录号。
⑵采用链接文件结构,只有读出一个物理块信息后才能从链接字中得知下一个物理块号。所以,当用户要在文件中插入一些信息时,文件系统必须多次地请求启动磁盘读出信息才能做插入工作。
• MS-DOS操作系统对链接文件结构作了改进,它是把所有的链接指针集中在一起,存放在文件定位表FAT中。查找链接字时不必读出物理块信息可直接从FAT中得到。
• 其设计思想是:
假定磁盘上共有N个物理块可供使用,FAT就有N项,初始化时为全“0”,表示对应的物理块均可使用,当要存放文件时,从FAT中寻找为“0”的项,其对应的物理块用来存放文件信息,把文件的链接指针(指出物理块号)登记在FAT中,文件第一块块号登记在文件目录中。
⑶假定磁盘存储空间共有32个物理块,模拟设计文件定位表FAT。文件定位表可以用一个一维数组FAT[0-31]来定义,其中一个元素与一个物理块对应。当FAT[i]=0时,表示第i块为空闲块;当FAT[i]=FFF时,表示链接文件到第i块结束;当在0~FFF时,其值指示链接文件中下一个物理块号。
⑷每个物理块只能存放一个逻辑记录,设计一个程序把文件的逻辑结构模拟转换成MS-DOS的链接结构。
• 要求保存一个已经在主存中的文件时,给出文件名和文件的逻辑记录长度及个数,对一个已经保存的文件,允许用户插入新记录。用键盘输入来模拟用户的要求,输入信息为:
“存” 文件名 逻辑记录长度 逻辑记录个数
“插入” 文件名 逻辑记录号
• 模拟程序的算法如下图所示:
⑸ 假设系统中已经有两个链接文件,其链接情况由FAT表指出(链接情况学生自定),现又要保存一个新文件,然后对已保存的文件插入一个新记录。运行你所设计的程序,观察其结果。
2、第二题:模拟便于直接存取的索引文件结构
【提示】
⑴索引文件像链接文件一样,文件的逻辑记录信息可存放在非连续的磁盘存储空间中。但这些存放逻辑记录的存储空间不是按链表的形式链接在一起的,而是采用索引表来指出逻辑记录存放的物理位置。
文件目录与索引表的关系如下图所示:
⑵建立索引文件的过程是:寻找一些空闲物理块;逻辑记录存入这些物理块中;把逻辑记录与物理块的对应关系登记在索引表中。
四、实验内容
1.算法设计
第一题:MS-DOS操作系统
【定义的数据结构】
int FAT[32]; //文件定位表 FAT
int blocks = 32; //可供使用的物理块数量
//文件名列表和文件起始块号一一对应
string name[32]; //文件名列表
int start[32]; //文件起始块号
int filenum = 0; //文件数量
【存文件操作】
首先输入文件名,判断是否有同名的文件,再输入逻辑记录长度,查内存剩余的空间是否够存储。如果条件均满足,则开始存储文件:
1.利用while循环找空闲的内存块
2.最先存储文件起始块号
3.再利用for循环根据逻辑记录长度依次存储空闲块号
4.最后修改存储空间大小
【插入文件操作】
首先输入文件名,判断是文件是否存在,再查内存剩余的空间是否够存储,最后输入要插入的位置同时判断插入的文件逻辑位置是否合理。如果条件均满足,则开始插入文件:
1.利用while循环找空闲的内存块
2.如果要在第一个位置插入,则更新起始块号
3.不是在第一个位置插入则利用一个for循环来依次修改插入位置后的索引。
4.最后修改存储空间大小
第二题:模拟便于直接存取的索引文件结构
【定义的数据结构】
int Disk[1024]; //磁盘空间
int DiskSize = 1024; //磁盘空间大小
int FileAdress = 0; //目录
int IndAdress = 1000; //索引表
//定义文件结构体
struct File
{
string file_name; //文件名
int file_size; //文件大小
int ind_adress; //索引表地址
int* block; //物理块号
};
//定义用户结构体
struct User
{
string user_name; //用户名
int con_adress; //文件目录地址
vector<File> file;//用户文件
};
【创建文件操作】
首先输入用户名,再输入文件名,判断文件是否重名,最后输入文件大小,判断磁盘空间是否足够,开始存储工作:
1.利用while循环找磁盘中空闲的块
2.利用for循环依次修改磁盘空间数组中的内容(为1表示占用),同时记录文件的物理块号
3.区分文件是已有用户添加还是新建用户添加
4.更改磁盘可用空间大小
【删除文件操作】
首先输入文件名,判断文件是否存在,开始删除工作:
1.利用for循环
2.更新磁盘空间数组中记录的数据(为0表示空闲),删除vector容器中的该文件
3.更改磁盘可用空间大小
2.源代码
第一题:MS-DOS操作系统
#include <iostream>
using namespace std;
int FAT[32]; //文件定位表 FAT
int blocks = 32; //可供使用的物理块数量
//文件名列表和文件起始块号一一对应
string name[32]; //文件名列表
int start[32]; //文件起始块号
int filenum = 0; //文件数量
//初始化FAT
void FAT_Init()
{
for (int i = 0; i < 32; i++)
{
FAT[i] = 0;
}
FAT[0] = -2;//将第一位设置为 FDF
FAT[1] = -1; //将第二位设置成所有文件的结束 FFF
blocks -= 2;
}
//判断是否有相同文件名的条目
int IsFileIn(string a)
{
for (int i = 0; i < filenum; i++)
{
if (name[i] == a) return i + 1;
}
return 0;
}
//存文件操作
void Save_File()
{
string filename;
cout << "请输入文件名: ";
//查找是否有同名的文件
while (cin >> filename)
{
if (IsFileIn(filename)) cout << "已有同名文件,请重新输入:";
else break;
}
int num;
cout << "请输入逻辑记录长度: ";
//检查内存剩余的空间是不是够存储
while (cin >> num)
{
if (num > blocks) cout << "内存剩余的空间不足,请重新输入:";
else break;
}
//记录文件名称
name[filenum] = filename;
int pos = 0, lastpos = 0;
//找空的内存块存储文件,同时更新索引
for (int i = 0; i < num; i++) {
//找到一个空的位置
while (FAT[pos] != 0)
pos++;
if (i == 0) start[filenum] = pos;
else FAT[lastpos] = pos;
lastpos = pos;
pos++;//从下一个块开始找,空出当前地址留待使用
blocks--;//剩余的块少一个
}
//将文件的结尾执行统一的结束符 FFF
FAT[lastpos] = -1;
filenum++;
cout << "文件存储成功!" << endl;
}
//插入文件操作
void Insert_Block()
{
string filename;
cout << "请输入文件名:";
//查找文件是否存在
while (cin >> filename)
{
if (!IsFileIn(filename)) cout << "该文件不存在,请重新输入:";
else break;
}
int pos = IsFileIn(filename) - 1;
int num;//将这个块插入到文件的第 num 个位置
//判断是否有空间存储这个插入的块
if (blocks == 0) {
cout << "无空闲块,无法插入!" << endl;
return;
}
cout << "请输入要插入的文件逻辑位置:";
//判断插入的文件逻辑位置是否合理
int m = start[pos], cmp = 1;
while (FAT[m] != -1)
{
m = FAT[m];
cmp++;
}
while (cin >> num)
{
if (num > cmp + 1) cout << "要插入的文件逻辑位置不存在,请重新输入:";
else break;
}
//找一个空闲的位置来存放要插入的块
int p = 0;
while (FAT[p] != 0)
p++;
//如果要在第一个位置插入,则更新起始块号
if (num == 1)
{
FAT[p] = start[pos];
start[pos] = p;
}
//不是在第一个位置插入
else
{
int temp = start[pos];
for (int i = 1; i < num - 1; i++)
{
temp = FAT[temp];
}
FAT[p] = FAT[temp];
FAT[temp] = p;
}
blocks--;//更新剩余的内存块
cout << "插入文件块成功!" << endl;
}
//输出文件目录和FAT
void Info()
{
cout << "文件目录如下:" << endl;
cout << "文件名\t起始块号" << endl;
for (int i = 0; i < filenum; i++)
{
cout << name[i] << "\t" << start[i] << endl;
}
cout << "文件定位表(FAT)如下:" << endl;
cout << "索引\t内容" << endl;
for (int i = 0; i < 32; i++)
{
cout << i << "\t ";
if (FAT[i] == -2) cout << "FDF" << endl;
else if (FAT[i] == -1) cout << "FFF" << endl;
else cout << FAT[i] << endl;
}
}
//功能选择清单
void choice_list()
{
system("cls");
cout << "*******************************" << endl;
cout << "MS-DOS磁盘文件存储管理菜单:" << endl;
cout << "0.结束操作,退出系统 " << endl;
cout << "1.向FAT中存储一个文件 " << endl;
cout << "2.向FAT中的文件插入数据 " << endl;
cout << "3.输出当前文件目录以及FAT信息" << endl;
cout << "*******************************" << endl;
cout << "请输入你要执行的操作的编号(数字): ";
}
int main()
{
FAT_Init(); //初始化FAT表
choice_list();
int choice;
while (cin >> choice)
{
switch (choice)
{
case 0:
cout << "运行结束,系统退出" << endl;
return 0;
//退出系统
case 1:
Save_File();
//存文件
break;
case 2:
Insert_Block();
//在指定的文件中的特定位置插入一个块
break;
case 3:
Info();
//显示当前的系统信息
break;
default:
cout << "输入不正确,请重新输入!" << endl;
//功能选择不正确,重新选择
}
system("pause");
choice_list();
}
}
第二题:模拟便于直接存取的索引文件结构
#include <iostream>
#include <vector>
using namespace std;
int Disk[1024]; //磁盘空间
int DiskSize = 1024; //磁盘空间大小
int FileAdress = 0; //目录
int IndAdress = 0; //索引表
//定义文件结构体
struct File
{
string file_name; //文件名
int file_size; //文件大小
int ind_adress; //索引表地址
int* block; //物理块号
};
//定义用户结构体
struct User
{
string user_name; //用户名
int con_adress; //文件目录地址
vector<File> file;
};
vector<User> u;//用户容器
//初始化磁盘
void Init()
{
for (int i = 0; i < 1024; i++)
Disk[i] = 0;
}
//判断是否有相同用户名的条目
int IsUserIn(string a)
{
for (int i = 0; i < u.size(); i++)
{
if (u[i].user_name == a) return i + 1;
}
return 0;
}
//判断是否有相同文件名的条目
int IsFileIn(string a)
{
for (int i = 0; i < u.size(); i++)
{
for (int j = 0; j < u[i].file.size(); j++)
{
if (u[i].file[j].file_name == a) return i + 1;
}
}
return 0;
}
//存文件操作
void Save_File()
{
cout << "请输入用户名:";
string user;
cin >> user;
cout << "请输入文件名:";
string file;
while (cin >> file)
{
if (IsFileIn(file)) cout << "该文件已存在,请重新输入:";
else break;
}
cout << "请输入文件大小:";
int size;
cin >> size;
if (size > DiskSize) cout << "磁盘空间不足,请删除部分文件" << endl;
else
{
File f;
IndAdress++;
f.ind_adress = IndAdress;
f.file_name = file;
f.file_size = size;
f.block = new int[size];
int cmp = 0;
for (int i = 0; i < size; i++)
{
while (Disk[cmp] != 0)
cmp++;
f.block[i] = cmp;
Disk[cmp] = 1;
}
//已有用户添加文件
if (IsUserIn(user))
{
int n = IsUserIn(user) - 1;
u[n].file.push_back(f);
}
//新建用户添加文件
else
{
User v;
FileAdress++;
v.con_adress = FileAdress;
v.user_name = user;
v.file.push_back(f);
u.push_back(v);
}
DiskSize -= size;
cout << "文件存储成功!" << endl;
}
}
//删除文件操作
void Del_File()
{
cout << "请输入要删除的文件名:";
string file;
while (cin >> file)
{
if (!IsFileIn(file)) cout << "该文件不存在,请重新输入:";
else break;
}
int n = IsFileIn(file) - 1;
for (vector<File>::iterator it = u[n].file.begin(); it != u[n].file.end(); it++) {
if ((*it).file_name == file)
{
for (int i = 0; i < (*it).file_size; i++)
{
Disk[(*it).block[i]] = 0;
}
DiskSize += (*it).file_size;
u[n].file.erase(it);
break;
}
}
cout << "文件删除成功!" << endl;
}
//输出文件目录和FAT
void Info()
{
cout << "磁盘剩余空间:" << DiskSize << endl;
for (int i = 0; i < u.size(); i++)
{
cout << "*用户名:" << u[i].user_name << "\t文件目录地址:0x" << u[i].con_adress << endl;
for (int j = 0; j < u[i].file.size(); j++)
{
cout << " ·文件名:" << u[i].file[j].file_name << "\t索引表地址:0x" << u[i].file[j].ind_adress << "\n |物理块号:";
for (int k = 0; k < u[i].file[j].file_size; k++)
{
cout << u[i].file[j].block[k] << " ";
}
cout << endl;
}
}
}
//功能选择清单
void choice_list()
{
system("cls");
cout << "*******************************" << endl;
cout << "MS-DOS磁盘文件存储管理菜单:" << endl;
cout << "0.结束操作,退出系统 " << endl;
cout << "1.添加文件 " << endl;
cout << "2.删除文件 " << endl;
cout << "3.输出当前用户信息及文件索引表" << endl;
cout << "*******************************" << endl;
cout << "请输入你要执行的操作的编号(数字): ";
}
int main()
{
Init(); //初始化磁盘
choice_list();
int choice;
while (cin >> choice)
{
switch (choice)
{
case 0:
cout << "运行结束,系统退出" << endl;
return 0;
//退出系统
case 1:
Save_File();
//添加文件
break;
case 2:
Del_File();
//删除文件
break;
case 3:
Info();
//显示当前文件信息和索引表
break;
default:
cout << "输入不正确,请重新输入!" << endl;
//功能选择不正确,重新选择
}
system("pause");
choice_list();
}
}
3.编译结果
第一题:MS-DOS操作系统
【初始化结果】
【添加两个文件file1、file2】
无法添加同名文件:
查看当前文件目录和FAT:
【向file1第一个位置插入数据】
如果要插入的文件不存在:
查看当前文件目录和FAT:
【向file2最后一个位置插入数据】
如果要插入的逻辑地址不正确:
查看当前文件目录和FAT:
第二题:模拟便于直接存取的索引文件结构
【初始化一些用户和文件】
如果文件名重复:
查看索引表:
【删除文件后添加新文件】
如果文件不存在:
重新创建文件file1并给用户use4:
查看索引表:
五、思考题
1.链表文件结构和索引文件结构各自的优缺点是什么?
答:
链式结构:
优点:提高了磁盘空间利用率,不需要为每个文件预留物理块;有利于文件插入和删除;有利于文件动态扩充。
缺点:存取速度慢,不适于随机存取;当物理块间的连接指针出错时,数据丢失;更多的寻道次数和寻道时间;链接指针占用一定的空间,降低了空间利用率。
索引结构
优点:不需要为每个文件预留物理块。既能顺序存取,又能随机存取。满足了文件动态增长、插入删除的要求。
缺点:较多的寻道次数和寻道时间。索引表本身带来了系统开销。如:内外存空间,存取时间等。
2.当文件较大时,使得索引表也较大,如果索引表的大小超过了一个物理块,如何进行索引表的存取?
答:
如果索引表的大小超过了一个物理块,可以采用间接索引(多重索引)的方式来解决,也就是在索引表所指的物理块中存放的不是文件信息,而是装有这些信息的物理块地址,这样就对索引表的长度没有限制了,但是同时也会增加很多空间上的开销。如果是使用分页式管理位示图算法,文件存储的时候是按照块来分割的,内存的空间也是按照块来分割的,所以就一定是能够充分利用内存空间的,不会造成浪费。但是同时,这种算法实现起来复杂度较高,虽然寻找空闲块比较简单,但是文件的读取和存储都是分块进行的,而且位置之间没有顺序性,所以读取和存储的寻址和处理的时间代价很大,算法复杂度较高。另外,这种算法是需要页表来进行维系的,页表也需要存储,会造成空间的浪费,如果内存分块块比较小,数量很多,会导致页表非常大,极限情况下,内存会被页表完全占领,造成极大的浪费,效率受到影响。
六、实验总结
通过本次的课程设计学习对 dos 文件的管理系统有了初步的了解。通过此次的课程设计使我更加明白设计的思路和需求分析十分重要甚至比代码用时更长。此次课程设计锻炼了自己的动手设计能力和分析程序的能力,为以后的程序开发打下基础。在设计与开发的过成中遇到了种种困难,但是都没有放弃,通过各种讨论与交流,最终完成此次课程设计。感谢老师给了一次锻炼的机会。
通过模拟DOS文件的建立和使用情况,理解磁盘文件的物理结构。对文件管理是操作系统中重要的内容之一,不同的文件系统提供了不同的物理结构有了深刻理解,同时深入理解文件的物理结构与存取方法之间的关系,以便更好的理解文件系统的概念。
本次课程课程设计使用了C++语言所以对C++的使用学习又有了近一步的掌握, 同时对以前学习C++语言中发现了很多问题,能够发现问题并解决问题也可以是一种成功。操作系统的重要性不言而喻,相信凭这次的课程设计,可让我在操作系统的功能和方法的方面有了清醒的认识和提高。