华南农业大学操作系统课设(模拟磁盘文件系统实现)(JavaFX)(单人课设)

展示效果的视频

华南农业大学18级本科软件工程-操作系统课设(模拟磁盘文件系统实现)-JavaFX

题目要求+代码+报告+展示视频的下载地址

下方两个链接内容是一样的:
大三上-操作系统课设(模拟磁盘文件系统实现)-JavaFX · wu/华南农业大学本科-18级软件工程-一些课设 - 码云 - 开源中国

华南农业大学18级本科软件工程-操作系统课设(模拟磁盘文件系统实现)-JavaFX.zip文档类-Java文档类资源-CSDN下载

提示:建议直接看代码,报告因为有报告模板和页数要求,所以写了很多。

实验报告

一、需求分析

(1)输入的形式和输入值的范围;

1、输入的形式

建立文件create_file(文件名,文件后缀名,文件属性,要建立文件的目录的绝对路径)。
打开文件open_file(文件绝对路径,打开方式(0读,1写))。
关闭文件close_file(文件绝对路径)
读文件String read_file(Path path)/read_file(文件绝对路径,读取长度(字节))
写文件(追加)write_file(文件绝对路径,写的内容,写长度(字节))
删除文件delete_file(文件绝对路径)。
显示文件内容typefile(文件绝对路径)。
改变文件属性change(文件绝对路径,文件新属性)。

建立目录md(目录名字,指向要建立的目录所在的文件夹的路径)。
显示目录内容dir(指向要打开的目录的路径,包含所有<文件/目录的目录项,显示的图标>键值对的HashMap对象)。
删除空目录rd(指向要删除的目录的路径)。

2、输入值的范围

建立文件FileMsg create_file(String name, String type, byte attribute, ArrayList path)
打开文件int open_file(ArrayList path, int operateType)
关闭文件void close_file(ArrayList path)
读文件String read_file(ArrayList path, int length)
写文件(追加)void write_file(ArrayList path, byte[] content, int length)
删除文件int delete_file(ArrayList path)
显示文件内容String typeFile(ArrayList path)
改变文件属性int change(ArrayList path, byte newAttribute)

建立目录FileMsg md(String name, ArrayList path)
显示目录内容HashMap<FileMsg, Button> dir(ArrayList path, HashMap<FileMsg, Button> map)
删除空目录int rd(ArrayList path)

(2)输出的形式;

建立文件、建立目录:返回建立的文件/目录的目录项,即FileMsg的对象。
删除文件、改变文件属性、删除空目录:成功返回1,失败返回-1。
打开文件:成功返回打开文件在打开表里的索引(0-4),失败返回-1。
读文件、显示文件内容:返回字符串。
显示目录内容:返回该目录下的所有<文件/目录的目录项,显示的图标>键值对的HashMap对象。

(3)程序所能达到的功能;

1、点击新建按钮:

建立(文件/目录):三选一的单选,输入名字的文本框

2、右击文件:

读文件
写文件(追加):不能读文件
关闭文件
删除文件
显示文件内容
改变文件属性

3、右击目录:

显示目录内容(打开目录)
删除空目录

4、点击后退按钮:回上一目录

(4)测试数据

正确的输入:最多输入3个字母/数字,可以把正确显示文件名/目录名。

错误的输入:输入文件名/目录名时,如果输入“$”、“.”、“/”,会给出错误提示,如果输入多于3个字母时,会只取前3个字母作为文件名/目录名,如果输入汉字或中文的标点符号,文件名显示出来会是乱码。

二、概要设计

1、抽象数据类型的定义

在这里插入图片描述

2、主程序的流程

在这里插入图片描述
3、各程序模块之间的层次(调用)关系
在这里插入图片描述

三、详细设计

1、数据类型

直接用的Java给的ArrayList和HashMap来实现各种操作。

ArrayList基于数组实现。get(int index)方法,获取指定索引位置的元素,时间复杂度为O(1)。add(E e)方法,添加元素到末尾,平均时间复杂度为O(1)。remove(int index)方法,
删除指定索引位置的元素,时间复杂度为O(n)。因为我每次删除都是只删除末尾的元素,所以在我的程序中,删除操作复杂度是O(1)。

HashMap底层是散列表加链表/红黑树,但我程序中没有用到它的优秀特性,我是直接遍历所有元素的。

2、主要函数的伪码算法

FileMsg 建立文件create_file(文件名,文件后缀名,文件属性,要建立文件的目录的绝对路径)
{
得到路径指向的目录的起始盘块号
查找空闲盘块
如果未找到路径所指向的文件夹、用户想建立属性为只读的文件、命名重复、磁盘空间已满、文件夹已经有8个文件/文件夹,这些情况会创建文件失败,给出提示,返回null
把文件的目录项写入磁盘
更新FAT
给文件末尾添加文件结束符’#’
返回该文件的FileMsg对象
}

int 打开文件open_file(文件绝对路径,打开方式(0读,1写))
{
得到路径指向的文件的起始盘块号
如果未找到路径所指向的文件夹,打开失败,给出提示,返回-1
该文件已在打开文件列表中时。如果打开方式一致,打开成功;如果打开方式不一致,那么打开失败,给出提示,返回-1
如果文件打开列表已满、找不到该文件在其目录中的索引、不能以写方式打开只读文件,打开失败,给出提示,返回-1
给已打开文件表添加表项
如果是已读方式打开,设置读指针指向文件开头
如果是以写方式打开,设置写指针指向文件的末尾,就是指向‘#’。方法:读入该文件的起始盘块到缓冲区,寻找该盘块中有没有’#‘,没有则通过FAT表找到该文件的下一盘块,读入缓冲区,继续查找’#‘,直到找到’#'或者文件结束。
返回已经打开的文件在文件打开表中的索引(0-4)
}

void 关闭文件close_file(文件绝对路径)
{
得到路径指向的文件的起始盘块号
对比路径指向的文件的文件起始盘块号和文件打开表中的每个表项的文件起始盘块号,一致则从文件打开列表中删除该表项
}

String 读文件String read_file(Path path)/read_file(文件绝对路径,读取长度(字节))
{
如果打开文件失败,则读取失败,返回null
新建byte数组,大小为:文件所占盘块数* 64,要保证装下整个文件
一个盘块一个盘块地读入整个文件,把文件结束符‘#’前的内容装入到byte数组
修改读指针指向‘#’
比较用户要读取的字节数和文件实际的字节数,得到小的那个数字,返回byte数组的这么多字节组成的字符串
}

void 写文件(追加)write_file(文件绝对路径,写的内容,写长度(字节))
{
如果打开文件失败,写入失败
计算需要的盘块数,计算空闲的盘块数
如果磁盘容量不够,写入失败
修改FAT表,占下这些用于追加的盘块
读出文件的起始盘块到缓冲区
循环:根据写指针来把要追加的内容一个字节一个字节写入缓冲区,当发现缓冲区写满的时候,就把缓冲区写入磁盘。在这期间要不断修改写指针,并且修改文件打开表里的文件占用字节数。
最终:文件字节长度不包括结束符‘#’,文件写指针最终指向‘#’
设置追加后文件占用的盘块数
}

int 删除文件delete_file(文件绝对路径)
{
如果未找到路径所指向的文件、文件已经打开,删除失败,返回-1
释放文件占用的数据块(在FAT表中把这一块清0)
删除该文件在其目录中的文件索引。方法:如果该目录项位于0-6,则把该目录里其后面的目录项顺次前移,并且把最后一个目录项设为空,即写入’$'。如果该目录项位于7,那么直接把该目录项设为空。
返回1,表示成功
}

String 显示文件内容typefile(文件绝对路径)
{
得到该文件的目录项,把其中的信息组织成字符串返回
}

int 改变文件属性change(文件绝对路径,文件新属性)
{
如果该文件已经打开,那么失败,返回-1
把新属性写入磁盘,覆盖旧属性
返回1,表示成功
}

FileMsg 建立目录md(目录名字,指向要建立的目录所在的文件夹的路径)
{
如果未找到路径所指向的文件夹、命名重复、磁盘空间已满、文件夹已经有8个文件/文件夹,这些情况会创建目录失败,给出提示,返回null
把文件的目录项写入磁盘
更新FAT
把这个新建目录的每个目录项设置为空,即在每个目录项第一个字节加’$’
返回该目录的FileMsg对象
}

HashMap<FileMsg, Button> 显示目录内容dir(指向要打开的目录的路径,包含所有<文件/目录的目录项,显示的图标>键值对的HashMap对象)
{
HashMap<FileMsg, Button> child=new HashMap<FileMsg, Button>()
得到该目录下所有文件/目录的起始盘块号
遍历所有起始盘块号,对每一个起始盘块号,得到它的对应的<FileMsg, Button>键值对,加入到child里。
返回child
}

int 删除空目录rd(指向要删除的目录的路径)
{
如果目录非空,删除失败,返回-1
回收目录占用的磁盘,在FAT表中把这一块清0
删除该目录在其目录中的索引。方法:如果该目录项位于0-6,则把该目录里其后面的目录项顺次前移,并且把最后一个目录项设为空,即写入’$'。如果该目录项位于7,那么直接把该目录项设为空。
返回1,表示成功
}

3、函数的调用关系图

在这里插入图片描述

四、调试分析

(1)调试过程中遇到的问题是如何解决的以及对设计与实现的讨论和分析;

1、调试过程中遇到的问题是如何解决的

就打断点调试,看看变量都符不符合自己的预期。把代表磁盘的文件用Notepad++打开,看每次操作后对文件的修改符不符合预期。

  • 增加目录项函数:用户只输入一个字符要把三个字符放文件里,空指针错误。解决方法:用户没写够3个字符,没写够的用’\0’填充。
  • 比较是否有同名文件/文件夹的函数:1、==改equals()。2、取出名字时取出了’\0’,导致明明命名重复却被认为命名没有重复。解决方法:取名字时不取出’\0’就好。
  • 两次读同一文件,第一次读文件没有问题,第二次读文件出现NegativeArraySizeException错误,因为读文件函数中调用了打开文件函数,而打开文件函数中,当它发现文件已经以要求方式打开时就会直接去读,没有重设读文件指针指向的位置,导致读文件指针的dnum指向的磁盘号为-1,最终因为没有读取导致出错。解决方法:每次读取后在读取函数里设置读指针,避免下次读取出错。
  • Negative seek offset:写入文件时,修改FAT表没有改好,导致文件写指针的Dnum得到-1,往下标为Dnum的盘块里写入一个数组时就会出这个错。
  • 两次写同一文件测试是也出现了一些问题。比如内部数据有用的缓冲区又读入了别的数据,导致写入结果有问题。解决方法:不读入别的数据就好。
2、对设计与实现的讨论和分析

一开始不知道怎么实现进入下一目录和回退到上一目录的功能。开始想法是把FileMsg类(那个对应8字节目录项的类)里面加上 ArrayList children=new ArrayList();和FileMsg parent;形成一棵可以往上走往下走的树,这样就可以实现回到上一目录(来到当前节点的父节点,显示父节点的所有孩子)和走进某一目录(来到当前节点的对应该目录的子节点,显示该子节点的所有孩子)。

但指导书已经给定了目录项的结构,那就没法改FileMsg类。然后看到指导书里里有一段“根据绝对路径名查找文件的方法”,大概意思是用绝对路径名如“root/x/z.txt”,读出根目录的盘块,把里面的所有目录项的名字和x比较,找到名字是x的目录的目录项,再读出x目录的盘块,取出里面的所有目录项,找到名字是z.txt的文件的目录项,这样来找到文件。

所以我没有用树,而是想用String来作为绝对路径,但如果直接输入一个字符串作为绝对路径,那么我得到这个字符串以后还要用函数根据“/”来切出路径中的每个名字,所以不如直接用ArrayList来作为文件绝对路径。用ArrayList作为绝对路径来找文件/目录,这样就可以实现回到上一目录(删除ArrayList里的最后一项,得到指向当前文件的父目录的路径,从根目录读出盘块开始,往下找到父目录的盘块,读出父目录里的所有目录项,得到父目录的所有孩子)和走进某一目录(把该目录的文件名加到ArrayList里,得到指向这一目录的路径,然后从根目录读出盘块开始,往下找到该目录,读出该目录所有目录项,得到该目录所有孩子)。于是我开始写,写了个函数int getBlockIndex(ArrayList path)是通过取出盘块目录项来找文件的,我发现我最终要得到的是路径所指向的文件/目录的起始盘块号(比如得到当前目录的起始盘块号,我就可以往里面添加目录项来实现新建文件/目录的功能。比如得到某一文件的起始盘块号,我就可以用起始盘块号判断该文件是否在文件打开表里,(因为不会有两个文件/目录有同一个起始盘块号),然后设置读指针指向起始盘块开始处,然后从这里开始读。),那么我直接以ArrayList path来作为绝对路径不是更方便吗。可以一句代码path.get(path.size() - 1).getStartBlock();就得到路径所指向文件的起始盘块号,于是我改用ArrayList path来作为绝对路径。

用ArrayList path作为绝对路径,思路是:绝对路径里只放目录,不放文件。放的是指向用户当前所在目录的路径。初始化时就ArrayList path=new ArrayList();,并且新建一个根目录的FileMsg放入路径中。用户通过“显示目录内容”进入某一目录时,就往path里加一个用户进入的目录的FileMsg对象,用户点后退按钮回到上一目录时,就把path末尾的FileMsg对象删除。这样,ArrayList path始终指向用户当前所在目录。


然后,要实现可视化,我用一个HashMap<FileMsg, Button> map,它是包含所有<文件/目录的目录项,显示的图标>键值对。当我需要找到某一目录下的所有文件时,我用path.get(path.size() - 1).getStartBlock();得到该目录的起始盘块号,然后读出该目录下的所有目录项,得到该目录的所有孩子的起始盘块号,然后用这些起始盘块号找到map里的有相同起始盘块号的FileMsg对象,得到对应的Button,这些Button是我要的该目录下的所有文件的图标。于是,我把界面上的所有图标撤下,放上我找到的这些图标,就实现了进入某个目录。map只用在两个地方,一个是后退按钮,一个是“显示目录内容”。

这里还有一个问题是,当磁盘内目录项的内容改变了,HashMap<FileMsg, Button> map和ArrayList path里的目录项FileMsg的内容是否需要改变呢?对于删除文件、删除空目录、创建文件、创建空目录,我们需要修改map,把map里对应的键值对删除或加入一个键值对。但对于其他操作,如修改属性、修改文件长度,map和path都是不用进行修改的,在对文件/目录的各种操作函数里,我们都是操作的磁盘里的量,读取信息也都读取的磁盘里的量,并没有用到FileMsg对象(除了open_file函数里,要写文件打开表的文件路径名那一项,用到了ArrayList path里每一个FileMsg对象的名字,但因为没有改名字的操作,也就不会有信息不一致的问题)。因为我们只用到了FileMsg对象里的起始盘块号这个量,其他的量我们不用,那它是什么值都无所谓。

突然发现对于HashMap<FileMsg, Button> map只用到了起始盘块号这个量,那么完全可以直接HashMap<Integer, Button> map。但我懒的改了。

(2)算法的时间复杂性和改进设想;

我是把所有目录项都放到了HashMap<FileMsg, Button> map里,当我要查找某个目录下的所有子文件的时候,我要遍历map才能找到。HashMap<FileMsg, Button> map里有n项的时候,HashMap<FileMsg, Button> dir(ArrayList path, HashMap<FileMsg, Button> map)函数,最好情况循环0n次,最坏情况循环8n次,时间复杂度O(n)。

其实可以

{
		Button button;
		Button parent;
		ArrayList<Button> children=new ArrayList<Button>();
}

以这种形式形成一棵树(不过Button之间没法比较,而且也不是二叉树,所以无法用TreeSet这样的容器,得自己写一棵树),空间会用很大,但时间复杂度O(1)(因为指针始终指向当前节点,前进/后退都只用在树上向下/向上移一格,和你总共有多少文件无关)。这棵树只用在两个地方,一个是后退按钮,一个是“显示目录内容”,每次只需要把指针指向当前节点的父节点或某个子节点就好。要维护这棵树只需要,创建/删除文件或目录的时候,相应修改下这个树就好。

其实可以TreeMap<Integer, Button> map,TreeMap基于红黑树实现的,增加、删除、查询操作的时间复杂度都是O(log n)。

其实可以HashMap<Integer, Button> map,HashMap底层是散列表加链表/红黑树,添加,删除,查找时间复杂度都是O(1)。Hash表在Java中,如果放用户自己定义的类,那么是按引用传递的。对于Java自己的包装类,那么是按值传递的。比如:

HashMap<Integer,Integer> map=new HashMap();
map.put(1,1);
System.out.println(map.get(1));//会输出1

也就是说,我们可以<起始盘块号,文件图标>这样来存放所有文件。

(3)设计过程的经验和体会;

没什么体会。

还有挺多能改的地方:比如String read_file(ArrayList path, int length)函数(不用读出整个文件。可以改成先比较下length和文件的长度,读小的那个字节数。减少读取时间)、void write_file(ArrayList path, byte[] content, int length)函数(可以把“#”加入到content这个字节数组里,一起写入。代码会少一点)。比如有些代码能提成一个单独的函数,使代码更好看。比如ArrayList getParentPath(ArrayList path),其实不需要这个函数,原因:参考网站:如何巧妙的使用ArrayList的Clone方法 - LyJs - 博客园。比如可以直接HashMap<Integer, Button> map。比如可以维护一棵树用于可视化。但我懒的改了。

(4)实现过程中出现的主要问题及解决方法。

看不懂题目,多看几遍。思路不清楚,看别人的代码是怎么实现的。写代码的时候要测试某个函数、实现某个功能,就建了两个测试项目测试。程序跑不起来或程序和预期结果不符,打断点调试。

五、用户使用说明

1、程序主界面:

在这里插入图片描述

2、点击新建按钮来新建目录/文件:

3、目录右键菜单:

在这里插入图片描述

4、文件右键菜单:

在这里插入图片描述

5、写文件:

在这里插入图片描述

6、读文件:

在这里插入图片描述

7、显示文件内容:

在这里插入图片描述

六、测试与运行结果

" "的ASCII码是32,转成16进制是20H。'\0’的ASCII码是0。‘#’的ASCII码是35,转成16进制是23H。
测试的部分输入可以看用户使用说明里的操作。

1、进入程序:磁盘初始化:先128个盘块全部清空,然后给FAT表的前三个字节填上255,然后给盘块2根目录的8个目录项都填上"$",表示这些是空目录项。
在这里插入图片描述

2、新建目录,目录名为“1”:FAT表,目录的目录项,目录所在的盘块。
在这里插入图片描述

3、新建文件,文件名为“1.t”:FAT表,文件的目录项,文件所在的盘块。‘#’的ASCII码是35,转成16进制是23H,用来表示文件结束。
在这里插入图片描述

4、写文件,写“让我过吧。”可以看到,中文字符用utf8编码转换,每个字符转换为3个字节。最后用23H表示文件结束。
在这里插入图片描述

5、改变文件属性。把普通文件变为系统只读文件。可以看到该文件的目录项里的文件属性从4变为了3。
在这里插入图片描述

6、删除目录:改了FAT表,改了根目录里的目录项。
在这里插入图片描述

7、删除文件:改了FAT表,改了根目录里的目录项。
在这里插入图片描述

  • 12
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值