多级文件系统
1 设计目的
为了加深对文件系统内部功能和实现过程的理解,设计一个模拟的多用户多级目录的文件系统,并实现具体的文件物理结构、目录结构以及较为完善的文件操作命令集。
2 设计内容
2.1系统操作
操作命令风格:本文件系统的操作命令风格将采用windows系统的文件操作命令风格,操作命令区分大小写,并能在用户错误地执行某些操作之后给出及时反馈,提供良好的交互体验。
具体的操作:本文件系统将实现以下功能操作:
2.2文件物理结构
本文件系统的文件物理结构将采用顺序结构,即将文件记录存储在一块连续的磁盘空间中。相应地,磁盘空间的分配也采用连续分配的方式。
2.3文件目录结构
本文件系统的文件目录结构将采用树形目录结构。树形目录结构的优点是可以更方便有效地管理用户的文件,特别是在多用户系统且用户的文件数量较多的情境中。树形目录结构也是目前最为流行的文件目录结构之一,比较符合大多数人的使用习惯。
2.4磁盘空间管理
本文件系统的磁盘空间管理采用位示图来进行管理,用户可以使用show命令来显示磁盘空间位示图以查看当前系统的磁盘空间使用情况。
2.5额外功能
本文件系统提供查看系统操作命令集的功能,用户可以使用help命令来查看本系统已实现的功能,优化用户的使用体验。
另外,本文件系统还提供了将创建的文件保存到电脑本地的功能,以便用户下次使用本系统时能查看自己所创建的文件。2.6编程语言和环境
本文件系统的编程语言采用Java语言,运行环境为JDK 1.8版本及以上。
3 设计步骤
3.1需求分析
3.1.1功能需求
本文件系统的功能模块将细分为三个模块:用户管理模块、文件管理模块、系统管理模块。各个模块的功能需求如表3.1~表3.3所示。
**表 3. SEQ 表 _ ARABIC \s 1 1 用户管理模块功能需求_
名称 | 详细描述 |
---|---|
用户注册 | 用户使用register命令注册一个系统用户,并要求输入用户名和密码 |
用户登录 | 用户使用login命令进行登录,需要输入用户名和密码进行身份校验 |
用户注销 | 用户使用logout命令注销当前已登录的用户 |
表 3.2 文件管理模块功能需求
名称 | 详细描述 |
---|---|
创建目录 | 用户使用mkdir命令创建一个文件目录,文件目录名不能和当前目录下的文件或文件目录重名,并且不区分大小写 |
列出文件 | 用户使用dir命令列出当前目录下的所有文件的详细信息,支持使用路径名 |
切换目录 | 用户使用cd命令切换到目标目录,路径名使用windows系统的风格 |
创建文件 | 用户使用create命令创建一个文件,文件名不能和当前目录下的文件或文件目录重名,并且不区分大小写 |
打开文件 | 用户使用open命令打开一个文件,支持使用路径名 |
读取文件 | 用户使用read命令读取一个已打开的文件,可向后读取和向前读取 |
写入文件 | 用户使用write命令向一个已打开的文件写入记录 |
关闭文件 | 用户使用close命令关闭一个已打开的文件 |
删除文件 | 用户使用delete命令删除一个存在的文件或文件目录,支持使用路径名 |
重命名文件 | 用户使用rename命令对一个文件或文件目录进行重命名,新的名称不能和当前目录下的文件或文件目录重名,并且不区分大小写 |
表 3.3 系统管理模块功能需求
名称 | 详细描述 |
---|---|
显示帮助 | 用户使用help命令查看当前系统支持的操作命令以及使用方法 |
显示磁盘位示图 | 用户使用show命令查看当前磁盘空间的位示图和其他信息 |
退出系统 | 用户使用exit命令退出文件系统,系统在退出前会保存用户信息、文件信息和磁盘空间信息 |
3.1.2性能需求
本文件系统具有较高的响应比,在文件信息不是特别多的时候能够做到0延迟响应。并且本文件系统运行所需的内存空间比较小,保存文件的大小与文件信息的多少成正比。
3.1.3交互需求
用户与本文件系统的交互在控制台中进行,并且可以方便地查看系统操作命令,降低了用户的学习使用成本。
本文件系统具有良好的信息展示功能,包括文件信息展示、文件内容展示、操作命令展示、位示图展示,使用户可以及时地获取自己所需要的信息。
本文件系统具有友好的错误信息反馈。在用户想要执行一项不被允许的操作时(如打开不存在的文件)能够给予用户正确、简洁的引导式的错误反馈。
3.2概要设计
3.2.1设计思想
本文件系统在设计上采用模块化的思想,将服务类型一致的功能划分到同一个模块,使项目整体上比较清晰。并且每个模块都提供对外调用的接口,这样就能让其他模块在需要的时候调用本模块的某些功能,实现复用。
3.2.2模块设计
本文件系统分为用户管理模块、文件管理模块和系统管理模块三个大模块。其中各个模块细分如下:
- 用户管理模块:负责提供用户注册、用户登录、用户注销三个功能。
- 文件管理模块:文件管理模块还可分为文件模块、文件目录模块以及磁盘模块三个小模块。其中:
- 文件模块:负责提供创建文件、打开文件、读取文件、写入文件、关闭文件、删除文件以及重命名文件共七个功能。
- 文件目录模块:负责提供创建目录、切换目录、列出文件以及解析路径共四个功能。
- 磁盘模块:负责提供存储记录、释放磁盘空间、读取记录、保存磁盘数据以及加载磁盘数据共五个功能。
- 系统管理模块:系统管理模块有一个下属模块:打印信息模块,该模块负责提供显示文件列表、显示文件内容、显示帮助列表以及显示位示图共四个功能。
具体的系统模块图如图3.1所示。
图3.1 系统模块图
3.2.3抽象数据类型定义
系统用户信息实体类定义如下:
java
public class User implements Serializable {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
文件控制块实体类定义如下:
java
public class FileControlBlock implements Serializable {
/**
* 是否是目录文件
*/
private boolean isDirectory;
/**
* 文件名(包括了拓展名)
*/
private String fileName;
/**
* 拓展名
*/
private String suffix;
/**
* 起始盘块号
*/
private Integer startBlock;
/**
* 所占用的盘块数
* 文件大小 = 一个盘块的大小 * 所占用的盘块数
*/
private Integer blockNum;
/**
* 文件属性:保护码列表
*/
private List<ProtectType> protectTypeList;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 最后一次修改时间
*/
private LocalDateTime updateTime;
}
树形目录结构实体类定义如下:
java
public class Directory implements Serializable {
/**
* 文件控制块
*/
private FileControlBlock fileControlBlock;
/**
* 在树形目录结构中的位置
*/
private Integer index;
/**
* 文件夹属性:子目录项集合
*/
private List<Directory> childDirectory;
/**
* 父目录项的位置
*/
private Integer parentIndex;
打开的文件信息实体类定义如下:
java
public class ActiveFile {
/**
* 文件控制块
*/
private FileControlBlock fileControlBlock;
/**
* 文件记录
*/
private List<Character> fileRecord;
/**
* 读指针
*/
private Integer readPtr;
/**
* 写指针
*/
private Integer writePtr;
}
虚拟磁盘空间实体类定义如下:
java
public class Disk implements Serializable {
/**
* List<Character>: 表示一个盘块
* List<List<Character>>: 表示所有盘块的集合,即一个磁盘
*/
private List<List<Character>> disk;
/**
* 表示存储在该磁盘上的系统用户集
*/
private Map<String, User> userMap;
/**
* 表示存储在该磁盘上的所有文件控制块
*/
private List<FileControlBlock> fileControlBlockList;
/**
* 表示存储在该磁盘上的树形结构目录,第0个元素为根目录
*/
private List<Directory> directoryStruct;
/**
* 表示存储在该磁盘上的磁盘位示图
*/
private Integer[][] bitmap;
}
3.2.4主程序的流程图
主程序的流程图如图3.2所示。
图3.2 主程序流程图
3.3详细设计
3.3.1系统算法IPO表
表3.4~表3.26展示了本文件系统的各个功能的具体实现算法IPO表,介绍了各功能模块的编号、模块、日期、作者、被调用模块、输入输出、数据处理与相关数据。如下所示:
表3.4用户注册IPO表
表3.5用户登录IPO表
表3.6用户注销IPO表
表3.7创建文件IPO表
表3.8打开文件IPO表
表3.9读取文件IPO表
表3.10写入文件IPO表
表3.11关闭文件IPO表
表3.12删除文件IPO表
表3.13重命名文件IPO表
表3.14创建目录IPO表
表3.15切换目录IPO表
表3.16列出文件IPO表
表3.17解析路径IPO表
表3.18存储记录IPO表
表3.19释放磁盘空间IPO表
表3.20读取记录IPO表
表3.21保存磁盘数据IPO表
表3.22加载磁盘数据IPO表
表3.23显示文件列表IPO表
表3.24显示文件内容IPO表
表3.25显示帮助列表IPO表
表3.26显示位示图IPO表
3.4调试分析
一开始在实现树形结构目录的时候,我的文件目录项实体类是这样的:
java
public class Directory implements Serializable {
/**
* 文件控制块
*/
private FileControlBlock fileControlBlock;
/**
* 文件夹属性:子目录项集合
*/
private List<Directory> childDirectory;
/**
* 父目录项
*/
private Directory parentDirectory;
}
在一个目录项之中,持有其父目录项的引用,如果该目录项是目录文件的话,还持有归属于它的子目录项的集合,这样便可以实现一个树形结构的目录。而在磁盘中只要持有一个对根目录的引用就可以查找到所有的目录项,这样是很方便的一种实现方案。
但是,这样的实体类在程序实际运行起来的时候是有问题的。假设有这样的情形:directoryA是一个表示目录文件的目录项,directoryB是一个表示数据文件的目录项,并且directoryB的父目录项是directoryA。这样的关系,在程序中表现为,directoryA的childDirectory属性中持有对directoryB的引用,而directoryB的parentDirectory属性持有对directoryA的引用,这样就造成了循环引用。
这样的循环引用问题,在Java的序列化和反序列化的时候表现的尤为明显。因为本文件系统可以将系统运行过程中的磁盘数据保存到本地文件中,也可以将本地文件中的磁盘数据加载到程序中,前者是通过序列化实现的,后者是通过反序列化实现的。但是由于循环引用的问题,在进行序列化和反序列化的时候,会报栈溢出的错误,导致磁盘数据加载不了(表现为找不到本该存在的目录项)。
当时发现这个问题的时候,我通过IDEA的debug功能一步一步地调试,最终定位到问题的根源以及想出了解决方案。改进后的文件目录项实体类如下所示:
java
public class Directory implements Serializable {
/**
* 文件控制块
*/
private FileControlBlock fileControlBlock;
/**
* 在树形目录结构中的位置
*/
private Integer index;
/**
* 文件夹属性:子目录项集合
*/
private List<Directory> childDirectory;
/**
* 父目录项的位置
*/
private Integer parentIndex;
}
这样进行了修改之后,在磁盘中要保存的是整个树形目录结构的所有目录项的列表,如下所示:
java
public class Disk implements Serializable {
...
/**
* 表示存储在该磁盘上的树形结构目录,第0个元素为根目录
*/
private List<Directory> directoryStruct;
...
}
3.5系统测试
3.5.1 help命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | help | 显示本系统的帮助列表 | help命令功能正常 |
实际测试结果如下图所示:
3.5.2 exit命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | exit | 退出本系统 | exit命令功能正常 |
实际测试结果如下图所示:
3.5.3 register命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | register | 注册用户失败:用户名不能为空 | 缺少[用户名]参数 |
无 | register pw | 注册用户失败:密码不能为空 | 缺少[密码]参数 |
[pw]这个用户没有被注册 | register pw 123 | 注册用户成功 | register命令功能正常 |
[pw]这个用户已被注册 | register pw 123 | 注册用户失败:该用户已存在 | [pw]是一个已存在的用户,不能重复注册 |
实际测试结果如下图所示:
3.5.4 login命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | login | 用户登录失败:该用户不存在 | 缺少[用户名]参数,系统会判定[用户名]为空字符 |
无 | login pw | 用户登录失败:密码错误 | 缺少 [密码]参数,系统会判定[密码]为空字符 |
[pw]这个用户已被注册,且密码不是456 | login pw 456 | 用户登录失败:密码错误 | [pw]这个用户的密码不正确 |
[unknown]这个用户没有被注册 | login unknown 123 | 用户登录失败:该用户不存在 | [unknown]这个用户不存在(没有注册过) |
[pw]这个用户已被注册,且密码是123 | login pw 123 | 登录成功 | 用户名和密码都正确,登录成功 |
实际测试结果如下图所示:
3.5.5 logout命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
已登录了某个用户 | logout | 注销成功 | logout命令功能正常 |
当前没有登录用户 | logout | 用户注销失败:当前没有登录用户 | logout命令功能正常 |
实际测试结果如下图所示:
3.5.6 show命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | show | 显示位示图信息 | show命令功能正常 |
实际测试结果如下图所示:
注意:以下命令能够正确执行前提都是要在已进行了登录的情况下,如果没有进行登录,系统会给出错误提示信息。所以下述命令的测试都是在已登录了的情况下进行的测试,此前提条件不再赘述,忘悉知。
3.5.7 mkdir命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | mkdir | 创建目录失败:文件名不能为空 | 缺少[目录名]这个参数,系统会判定目录名为空字符 |
当前目录下不存在[os]这个子目录 | mkdir os | 创建成功,使用[dir]命令可以查看到[os]这个目录的信息 | mkdir命令功能正常 |
当前目录下存在[os]这个子目录 | mkdir os | 创建目录失败:文件名重复 | 同一个目录中的文件不能重名 |
实际测试结果如下图所示:
3.5.8 dir命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | dir | 列出当前目录下面所有文件的信息 | dir命令功能正常 |
实际测试结果如下图所示:
3.5.9 cd命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | cd | 仍在当前目录 | 如果缺少[目录路径]参数,则停留在当前目录下 |
当前目录下存在[os]这个子目录 | cd os | 进入到os目录 | cd命令功能正常 |
当前目录下不存在[os]这个子目录 | cd os | 切换目录失败:找不到对应目录 | cd命令功能正常 |
存在上一级目录 | cd ... | 返回到上一级目录 | cd命令功能正常 |
实际测试结果如下图所示:
3.5.10 create命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | create | 创建文件失败:文件名不能为空 | 缺少[文件名]这个参数,系统会判定文件名为空字符 |
当前目录下不存在[a.txt]这个文件 | create a.txt | 创建文件成功,使用[dir]命令可以查看到[a.txt]的信息 | create命令功能正常 |
当前目录下存在[a.txt]这个文件 | create a.txt | 创建文件失败:文件名重复 | 同一个目录中的文件不能重名 |
当前目录下存在[a.txt]这个文件 | create A.txt | 创建文件失败:文件名重复 | 本系统的文件名不区分大小写 |
实际测试结果如下图所示:
3.5.11 open命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | open | 打开文件失败:文件名不能为空 | 缺少[文件名]这个参数,系统会判定文件名为空字符 |
当前目录下不存在[b.jpg]这个文件 | open b.jpg | 打开文件失败:文件不存在 | open命令功能正常 |
当前目录下存在[a.txt]这个文件 | open a.txt | 打开文件成功,显示出文件中的内容 | open命令功能正常 |
实际测试结果如下图所示:
3.5.12 close命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
当前存在已打开的文件 | close | 关闭成功 | close命令功能正常 |
当前不存在已打开的文件 | close | 关闭文件失败:当前没有文件被打开 | close命令功能正常 |
实际测试结果如下图所示:
3.5.13 write命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
当前不存在已打开的文件 | write,然后输入文件内容,以"###"结尾 | 写入文件失败:请先打开文件再进行写入 | write命令功能正常 |
当前存在已打开的文件 | write,然后输入文件内容,以"###"结尾 | 写入成功,控制台会回显文件的所有内容(包括刚刚写入的内容) | write命令功能正常 |
实际测试结果如下图所示:
3.5.14 read命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
当前存在已打开的文件 | read | 显示空字符 | 缺少[要读取的记录个数]参数,系统会判定要读取的记录个数为0 |
当前存在已打开的文件,且(当前读指针 + 5)< 文件长度 | read 5 | 显示读取出来的5个字符 | read命令功能正常 |
当前存在已打开的文件,且(当前读指针 - 4)>= 0 | read -4 | 显示读取出来的4个字符 | read命令支持向后读取 |
实际测试结果如下图所示:
3.5.15 delete命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | delete | 删除文件失败:文件名不能为空 | 缺少[文件名]参数,系统会判定文件名为空字符 |
当前目录下不存在[b.jpg]这个文件 | delete b.jpg | 删除文件失败:找不到对应文件 | delete命令功能正常 |
当前目录下存在[a.txt]这个文件 | delete a.txt | 删除成功,使用[dir]命令查看a.txt文件不存在 | delete命令功能正常 |
实际测试结果如下图所示:
3.5.16 rename命令测试
测试数据如下表:
前提条件 | 命令输入 | 执行结果 | 结果分析 |
---|---|---|---|
无 | rename | 重命名文件失败:文件名不能为空 | 缺少[文件名]参数,系统会判定文件名为空字符 |
无 | rename b.jpg | 重命名文件失败:文件名不能为空 | 缺少[新文件名]参数,系统会判定新文件名为空字符 |
当前目录下不存在[b.jpg]这个文件 | rename b.jpg c.mp3 | 重命名文件失败:文件不存在 | rename命令功能正常 |
当前目录下存在[a.txt]这个文件 | rename a.txt c.mp3 | 重命名成功,使用[dir]命令可查看a.txt已变为c.mp3 | rename功能正常 |
实际测试结果如下图所示:
3.6使用说明
3.6.1操作命令说明
本文件系统支持的所有操作命令已在"帮助列表"中列出。想要查看"帮助列表"时只要键入 help 命令即可,如下所示:
现在对本文件系统中的几种操作命令格式做说明:
①无参数命令:例如help、show、exit、dir等都是无参数命令。在使用无参数命令时,只需要键入对应的命令即可,不需要输入额外的参数。如果输入的额外的参数,系统会默认把它丢弃掉,也并不影响使用。
②单参数命令:例如mkdir、create、open等都是单参数命令。使用单参数命令时,输入对应的命令之后,要输入一个(或者至少一个)空格,然后输入正确的参数,最后回车即可。在上述"帮助列表"中的"用法"中,方括号框中表示的是要输入的参数。例如mkdir [目录名],该命令正确的用法是"mkdir test",表示创建一个名为"test"的新目录。
③多参数命令:例如register、login等都是要输入一个以上参数的命令。在使用多参数命令时,参数和参数之间要使用一个空格进行分隔开。另外,参数的个数要对应。例如在使用login [用户名] [密码],如果输入"login 123",系统会把"123"当成用户名来识别,而该命令缺少了密码这个参数,系统给出错误提示。
另外,对本文件系统采用的文件路径格式做如下说明:
①目录名与目录名(或文件名)之前用路径分隔符"/"分隔开。例如"cd test/word"表示进入到当前目录下的test文件夹下的word文件夹中。
②全部采用相对路径
③"..."表示上一级目录。例如"cd ..."表示回退到上一级目录。
3.6.2磁盘数据保存说明
本文件系统在启动的时候会加载"固定路径"下的"磁盘文件"中的磁盘数据,在正常退出的时候会保存磁盘数据到"固定路径"下的"磁盘文件"中。
"固定路径"是指程序运行文件所在目录中的save文件夹。"磁盘文件"是指save文件夹中的一个名为disk.ser的文件,本文件系统的所有磁盘数据都会保存进里面。如果本文件系统启动的时候找不到disk.ser文件(或save文件夹),系统会初始化磁盘,相当于一个新激活的系统。
本文件系统在正常退出(使用exit命令退出)的之前,会保存当前的磁盘数据到save文件夹中的disk.ser文件中。如果disk.ser文件不存在,会新建一个disk.ser文件;如果disk.ser文件存在,则会进行覆盖写入操作。
3 经验与体会
我的经验和体会是:
①在编程实现多级文件系统之前,要先确定好文件的逻辑结构、文件的物理结构、文件控制块信息、树形目录结构的表现形式、磁盘空间的表示显示、文件记录在磁盘中的表现形式、磁盘空间的分配方式、磁盘空间的管理形式等重要问题。只有思考了以上问题以及确定好具体的实现方案之后,才能着手与正式的编程实现,否则就在编程的同时遇到很多具有极强关联性的问题,导致了编程后期可能要对系统进行重构。
②在设计本文件系统的交互的时候,要考虑到用户的使用感受,应该使用户能以尽量简单的形式使用本系统,操作命令的输入不要过于冗余而繁杂。在这个方面可以参考很多优秀的操作系统的实现方式,比如Linux和Windows系统。
4 重要数据结构或疑难部分说明
4.1重要数据结构
大部分重要数据结构已经在之前的设计步骤中的需求分析的抽象数据类型定义小节中进行了说明,这里便不再赘述。关于树形结构目录的实现则在实体类Directory中。
4.2疑难部分说明
除了树形目录结构的具体实现比较难之外(该实现已在调试分析中进行说明),另一个疑难部分就是在存储文件记录时为文件记录分配磁盘空间和修改位示图的具体实现。存储文件记录的具体实现为:
java
public CommonResult<FileControlBlock> storeRecord(FileControlBlock fileControlBlock, List<Character> record) {
if (CollectionUtil.isEmpty(record)) {
return CommonResult.operateSuccess(null);
}
// 修改位示图,为重新分配盘块做准备
changeBitmapStatus(fileControlBlock.getStartBlock(), fileControlBlock.getBlockNum(), true);
// 计算存储该长度记录所需要的盘块数量
Integer requiredNum = Math.ceilDivide(record.size(), DiskConstant.BLOCK_SIZE);
int count = 0;
int startBlockId = DiskConstant.RECORD_START_BLOCK;
for (int i = 0; i < DiskConstant.BITMAP_ROW_LENGTH; i++) {
for (int j = 0; j < DiskConstant.BITMAP_LINE_LENGTH; j++) {
// 如果该盘块空闲
if (DiskConstant.BITMAP_FREE.compareTo(Memory.getInstance().getBitmap()[i][j]) == 0) {
if (count == 0) {
// 记下起始盘块号: 当前行 * 总列数 + 当前列
startBlockId = i * DiskConstant.BITMAP_LINE_LENGTH + j;
}
count++;
if (count == requiredNum) {
// 如果有足够的连续盘区供存储,则进行存储,并改变位示图的相应状态
storeToDisk(startBlockId, record);
changeBitmapStatus(startBlockId, requiredNum, false);
return CommonResult.operateSuccess(fileControlBlock.setStartBlock(startBlockId).setBlockNum(requiredNum));
}
} else {
// 因为是连续分配,如果该盘块不空闲则要重新计数
count = 0;
}
}
}
return CommonResult.operateFailWithMessage("[分配盘块失败]: 磁盘空间不足");
}
在该方法中调用了两个关键的方法:storeToDisk(startBlockId, record)和changeBitmapStatus(startBlockId, requiredNum, false)。
其中的storeToDisk(startBlockId, record)方法的作用是将文件记录存储在磁盘中。传入参数startBlockId表示起始盘块号,传入参数record表示待存储的文件记录。其具体实现为:
java
private void storeToDisk(Integer startBlockId, List<Character> record) {
int index = 0;
int blockId = startBlockId;
Disk disk = DataCache.getInstance().getDisk();
// 可以直接覆盖掉原来的磁盘中的记录
for (Character ch : record) {
if (index >= DiskConstant.BLOCK_SIZE) {
// 如果一个盘块的空间被用完了,则使用下一个盘块来进行存储
blockId++;
index = 0;
}
disk.getDisk().get(blockId).add(index, ch);
index++;
}
while (index < DiskConstant.BLOCK_SIZE && disk.getDisk().get(blockId).size() > index) {
// 擦除最后一个盘块中没有用到的空间
disk.getDisk().get(blockId).set(index, null);
index++;
}
}
其中的changeBitmapStatus(startBlockId, requiredNum, false)方法的作用是更改相应的位示图的状态(仅适用于连续分配的方式)。传入参数startBlockId表示起始盘块号,传入参数blockNum表示要更改的盘块数量,传入参数changeToFree表示要更改的状态(true-表示空闲状态,false-表示已分配状态)。其具体实现为:
java
private void changeBitmapStatus(Integer startBlockId, Integer blockNum, boolean changeToFree) {
if (Objects.isNull(startBlockId) || startBlockId < DiskConstant.RECORD_START_BLOCK ||
startBlockId >= DiskConstant.BLOCK_NUM || blockNum <= 0) {
return;
}
// 解析该盘块在位示图中的第几行
int row = startBlockId / DiskConstant.BITMAP_LINE_LENGTH;
// 解析该盘块在位示图中的第几列
int line = startBlockId % DiskConstant.BITMAP_LINE_LENGTH;
for (int i = 0; i < blockNum; i++) {
Memory.getInstance().getBitmap()[row][line] = changeToFree ? DiskConstant.BITMAP_FREE : DiskConstant.BITMAP_BUSY;
if (line >= DiskConstant.BITMAP_LINE_LENGTH - 1) {
line = 0;
row++;
} else {
line++;
}
}
}