【设计题目】
二级文件系统设计
【设计目的】
(1)本实验的目的是通过一个简单多用户文件系统的设计,加深理解文件系统的内部功能和内部实现。
(2)结合数据结构、程序设计、计算机原理等课程的知识,设计一个二级文件系统,进一步理解操作系统。
(3)通过分对实际问题的分析、设计、编程实现,提高学生实际应用、编程的能力。
【设计内容】
一、任务
为Linux系统设计一个简单的二级文件系统。要求做到以下几点:
1.可以实现下列几条命令:
login 用户登录
dir 列目录
create 创建文件
delete 删除文件
open 打开文件
close 关闭文件
read 读文件
write 写文件
cd 进出目录
2.列目录时要列出文件名,物理地址,保护码和文件长度
3.源文件可以进行读写保护
二、程序设计
1.要求实现的功能:
login 登录
ls 列目录
ll 列目录(详细)
mkdir 创建目录
mk 创建文件
rmdir 删除文件夹
rm 删除文件
open -ff 打开文件到内存中(最先适应存储分配算法)
open -zj 打开文件到内存中(最佳适应存储分配算法)
open -zh 打开文件到内存中(最坏适应存储分配算法)
close i 释放内存中第i个线程
cat 读文件
write -a f context 写文件(追加)
write -c f context 写文件(覆盖)
cd 进出目录
2.额外实现的功能:
cp f1 f2 复制文件夹f1变成f2
mv f1 f2 移动文件夹f1变成f2
attrib f r 修改文件f的权限为只读
attrib f w 修改文件f的权限为读写
head f n 读取文件f的前n行内容
tail f n 读取文件f的后n行内容
clear 清除屏幕
exit 退出系统
3.列目录时列出文件名、物理地址、保护码、文件长度、读写权限、修改日期、文件类型等
4.源文件可以进行读写保护,可以通过attrib命令修改文件权限为只读或读写。
【实验环境】
IntelliJ IDEA 2021.1.1 x64
【设计思路】
一、采用的数据结构
(一)主要数据结构——FileContext类
该类存储乐多级文件系统的实际存储路径、用户名、当前路径等,linux基本操作基本围绕着这个类展开,这也是本项目最重要的一个数据结构之一。
public class FileContext {
//多级文件系统的实际存储路径(该项目父结构的下一层./dir目录下)
public static String real = "." + File.separatorChar+ "dir" ;
//当前路径
public static String now="/";
//多级文件系统的用户名
public static String user="";
//是否使用目录
public static String userDirectory = "/"+user;
//字符集
public static String Charset = "UTF-8";
}
(二)主要数据结构——Memory类
Memory类是Java中的模拟内存,主要用来打开、关闭文件的。其中,size是内存块大小,lastFind是上次寻址结束的下表,threds、holes分别是内存块进程的双向链表和内存块分区的双向链表,MIN_SIZE是最小剩余分区大小。这里为了侧重展示重点,去掉了非核心的get、set方法,核心方法是getMemory申请一个size大小的内存,和releaseMemory释放内存,着两个方法也是实现动态分区存储管理的核心实现方法。
public class Memory {
private int size; //内存块大小
private int lastFind; //上次寻址结束的下标
private LinkedList<Thrd> thrds; //记录内存块中进程的双向链表
private LinkedList<Hole> holes; //记录内存块分区的双向链表
private static final int MIN_SIZE = 5; //最小剩余分区大小
public Memory(int size) {
this.size = size; //初始化内存大小
this.thrds = new LinkedList<>(); //初始化两个链表
this.holes = new LinkedList<>();
holes.add(new Hole(0, size)); //分区链表的首项为大小是内存大小的空闲分区
}
//size:申请大小 location:分配分区的下标 hole:location对应的分区
public Memory getMemory(int size, int location, Hole hole) {
//若分配后当前分区剩余大小大于最小分区大小,则把当前分区分为两块
if (hole.getSize() - size >= MIN_SIZE) {
Hole newHole = new Hole(hole.getHead() + size, hole.getSize() - size);
holes.add(location + 1, newHole);
hole.setSize(size);
}
thrds.add(new Thrd(10000 + (int)(89999 * Math.random()), 1, hole)); //模拟添加一个就绪状态的进程,此进程id随机生成(忽略id重复的情况哈)
hole.setFree(false); //设置当前分区为非空闲状态
System.out.println("成功分配大小为" + size + "的内存");
return this;
}
public Memory releaseMemory(int id) {
Thrd thrd = null; //记录此id对应进程(忽略进程id与分区id相同,但进程不同的情况哈)
if (id >= holes.size()) { //若id大于holes的表长度,则需要判断此id是否是进程id
boolean flag = false;
for (int i = 0; i < thrds.size(); i++) { //循环比对此id是否是thrds链表中进程的id
if (thrds.get(i).getId() == id) {
thrd = thrds.get(i);
flag = true;
break;
}
}
if (!flag) {
System.err.println("无此分区:" + id);
return this;
}
}
if (thrd != null) { //若是通过进程id释放内存,则用下列循环获取进程对应的hole对应holes链表的下标(获取分区id)
for (int i = 0; i < holes.size(); i++) {
Hole hole = holes.get(i);
if ((thrd.getHole().getSize() == hole.getSize()) && (thrd.getHole().getHead() == hole.getHead())) {
id = i;
break;
}
}
}
Hole hole = holes.get(id); //此id为分区id
if (hole.isFree()) {
System.out.println("此分区空闲,无需释放:\t" + id);
return this;
}
//用分区id释放thrd
for (int i = 0; i < thrds.size(); i++) {
Thrd thrd2 = thrds.get(i);
if ((thrd2.getHole().getSize() == hole.getSize()) && (thrd2.getHole().getHead() == hole.getHead())) {
thrds.remove(i);
break;
}
}
//如果回收分区不是尾分区且后一个分区为空闲, 则与后一个分区合并
if (id < holes.size() - 1 && holes.get(id + 1).isFree()) {
Hole nextHole = holes.get(id + 1);
hole.setSize(hole.getSize() + nextHole.getSize());
holes.remove(nextHole);
}
//如果回收分区不是首分区且前一个分区为空闲, 则与前一个分区合并
if (id > 0 && holes.get(id - 1).isFree()) {
Hole lastHole = holes.get(id - 1);
lastHole.setSize(hole.getSize() + lastHole.getSize());
holes.remove(id);
id--;
}
holes.get(id).setFree(true);
System.out.println("内存回收成功!");
return this;
}
}
(三)主要数据结构——Hole类
Hole类用于表示内存中各个分区,也表示进程对应的内存块,其中,head是小内存块的起始地址,size是小内存块的大小,isFree是小内存块的空闲状态。
public class Hole {
private int head; //小内存块的起始地址
private int size; //小内存块的大小
private boolean isFree; //小内存块的空闲状态
public Hole(int head, int size) {
this.head = head;
this.size = size;
this.isFree = true;
}
}
(四)主要数据结构——Thrd类
Thrd类主要用于描述进程,是用于记录进程信息和进程对应的内存块,其中id是进程号,state是进程状态,hole是进程对应的小内存块。
public class Thrd {
private int id; //进程id
private int state; //进程状态 0为空闲 1为就绪 2为执行 3为阻塞
private Hole hole; //进程所对应的小内存块
public Thrd(int id, int state, Hole hole) {
this.id = id;
this.state = state;
this.hole = hole;
}
}
二、主要的函数说明
(一)有哪些函数——实现Linux基本操作的接口
下面展示的是实现Linux基本操作的关键函数,完成了课设所有要求的同时,实现了约20个不同的linux命令。下面是接口部分的代码展示。我将在后面着重选择open、close、delete、write这四个函数来讲。
public interface FileManagerService {
Boolean login(String username,String password);
Boolean register(String username,String password);
String ls() throws Exception;
String ll() throws Exception;
Boolean attrib(String filename,String permission) throws IOException;
String cd(String arg) throws IOException;
Boolean cp(String filename,String newFilename) throws IOException;
Boolean mv(String filename,String newFilename) throws IOException;
Boolean mk(String filename) throws IOException;
Boolean rm(String filename) throws IOException;
Boolean rmdir(String filename);
Boolean mkdir(String filename) throws IOException;
String cat(String filename) throws IOException;
String help(String command) throws IOException;
String help() throws IOException;
String head(String filename, Integer n);
String tail(String filename, Integer n);
Boolean write(Integer mode,String filename,String context) throws IOException;
String open(String algorithmId,String filename);
String close(Integer thrdId);
default String getNowPath(){
String[] strings = FileContext.now.split(File.pathSeparator);
if(FileContext.now.startsWith(File.separatorChar+ FileContext.user)){
return FileContext.now.replace(File.separatorChar+ FileContext.user,"~");
}
return FileContext.now;
}
default String getRealPath(){
return FileContext.real + FileContext.now;
}
default void createUserDirectory(){
File file = new File(FileContext.real + File.separatorChar + FileContext.user);
if(!file.exists()){
file.mkdirs();
}
};
default boolean checkUserDirectory(){
File file = new File(FileContext.real + File.separatorChar + FileContext.user);
if(!file.exists()){
createUserDirectory();
return true;
}
return true;
}
default void init(){
if(checkUserDirectory()){
FileContext.now = File.separatorChar+ FileContext.user;
}
}
}
(二)主要函数——open()方法
open方法实现的核心功能为,将磁盘中的文件调度到内存中打开,同时使用动态分区分配算法,实现模拟操作系统中,内存的优化。
这里前4行有效代码都是类中的全局变量,在open方法中都有用到。
Partition partition = new Partition();
//初始化的内存大小
private final Integer memorySize = 1000;
//初始化一个内存
Memory memory = new Memory(memorySize);
//存储小内存块的链表
LinkedList<Hole> holeLinkedList = new LinkedList<>();
@Override
public String open(String algorithmId,String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
//要申请的大小
long totalSpace = 0;
//申请的大小
if(file.isDirectory()){
totalSpace = getFolderSize(file);
}else{
totalSpace = file.length();
}
Hole hole = new Hole(file.getAbsolutePath().hashCode(), (int) file.length());
holeLinkedList.add(hole);
//1--首次适应算法
if("-ff".equals(algorithmId)){
memory = partition.FF(memory, (int) totalSpace);
//2--最佳适应算法
}else if("-zj".equals(algorithmId)){
memory = partition.ZJ(memory,(int) totalSpace);
//3--最坏适应算法
}else if("-zh".equals(algorithmId)){
memory = partition.ZH(memory,(int) totalSpace);
}
String showThrds = partition.showThrds(memory);
String showMemory = partition.showMemory(memory);
return showThrds+"\n"+showMemory;
}
(三)主要函数——动态分区分配算法
1.首次适应算法
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。函数如下:
public Memory FF(Memory memory, int size) {
int sum = 0;
//循环内存中所有分区
for (int i = 0; i < memory.getHoles().size(); i++) {
sum++;
//为循环首次适应算法设置最后寻址的下标
memory.setLastFind(i);
Hole hole = memory.getHoles().get(i); //获得对应的分区
//若此分区空闲且大小大于申请的大小,则申请内存
if (hole.isFree() && hole.getSize() >= size) {
System.out.println("查找" + sum + "次");
return memory.getMemory(size, i, hole);
}
}
System.err.println("OUT OF MEMORY!");
return memory;
}
2.最佳分区分配算法
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
public Memory ZJ(Memory memory, int size) {
int findIndex = -1; //最佳分区的下标
int min = memory.getSize(); //min存储当前找到的最小的合适的分区大小
for (int i = 0; i < memory.getHoles().size(); i++) {
//memory.setLastFind(i);
Hole hole = memory.getHoles().get(i);
if (hole.isFree() && hole.getSize() >= size) {
//若当前找到的分区大小比min还要合适(剩余空间更小),则修改其值
if (min > hole.getSize() - size){
min = hole.getSize() - size;
findIndex = i;
}
}
}
if (findIndex != -1) { //若存在合适分区
return memory.getMemory(size, findIndex, memory.getHoles().get(findIndex));
}
System.err.println("OUT OF MEMORY!");
return memory;
}
3.最坏分区分配算法
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
public Memory ZH(Memory memory, int size) {
int findIndex = -1;
int max = 0;
for (int i = 0; i < memory.getHoles().size(); i++) {
Hole hole = memory.getHoles().get(i);
if (hole.isFree() && hole.getSize() >= size) {
if (max < hole.getSize() - size){
max = hole.getSize() - size;
findIndex = i;
}
}
}
if (findIndex != -1) {
return memory.getMemory(size, findIndex, memory.getHoles().get(findIndex));
}
return memory;
}
(四)主要函数——close()方法
主要函数有close调用的releaseMemory释放内存操作和showThrds、showMemory展示进程、内存的操作。
@Override
public String close(Integer thrdId) {
memory = memory.releaseMemory(thrdId);
String showThrds = partition.showThrds(memory);
String showMemory = partition.showMemory(memory);
return showThrds+"\n"+showMemory;
}
releaseMemory方法如下:
public Memory releaseMemory(int id) {
Thrd thrd = null; //记录此id对应进程(忽略进程id与分区id相同,但进程不同的情况哈)
if (id >= holes.size()) {//若id大于holes的表长度,则需要判断此id是否是进程id
boolean flag = false;
for (int i = 0; i < thrds.size(); i++) { //循环比对此id是否是thrds链表中进程的id
if (thrds.get(i).getId() == id) {
thrd = thrds.get(i);
flag = true;
break;
}
}
if (!flag) {
return this;
}
}
if (thrd != null) { //若是通过进程id释放内存,则用下列循环获取进程对应的hole对应holes链表的下标(获取分区id)
for (int i = 0; i < holes.size(); i++) {
Hole hole = holes.get(i);
if ((thrd.getHole().getSize() == hole.getSize()) && (thrd.getHole().getHead() == hole.getHead())) {
id = i;
break;
}
}
}
Hole hole = holes.get(id); //此id为分区id
if (hole.isFree()) {
return this;
}
//用分区id释放thrd
for (int i = 0; i < thrds.size(); i++) {
Thrd thrd2 = thrds.get(i);
if ((thrd2.getHole().getSize() == hole.getSize()) && (thrd2.getHole().getHead() == hole.getHead())) {
thrds.remove(i);
break;
}
}
//如果回收分区不是尾分区且后一个分区为空闲, 则与后一个分区合并
if (id < holes.size() - 1 && holes.get(id + 1).isFree()) {
Hole nextHole = holes.get(id + 1);
hole.setSize(hole.getSize() + nextHole.getSize());
holes.remove(nextHole);
}
//如果回收分区不是首分区且前一个分区为空闲, 则与前一个分区合并
if (id > 0 && holes.get(id - 1).isFree()) {
Hole lastHole = holes.get(id - 1);
lastHole.setSize(hole.getSize() + lastHole.getSize());
holes.remove(id);
id--;
}
holes.get(id).setFree(true);
return this;
}
两个show方法如下:
public String showMemory(Memory memory) {
String returnStr =
"-------------------------------------------\n"+
"分区编号\t分区始址\t分区大小\t空闲状态\t\n"+
"-------------------------------------------\n";
for (int i = 0; i < memory.getHoles().size(); i++){
Hole hole = memory.getHoles().get(i);
returnStr += i + "\t\t" + hole.getHead() + "\t\t" + hole.getSize() + " \t\t" + hole.isFree()+"\n";
}
returnStr += "-------------------------------------------\n";
return returnStr;
}
public String showThrds(Memory memory) {
String returnStr =
"-------------------------------------------\n"+
"进程编号\t进程状态\t进程起始地址\t进程大小\t\n"+
"-------------------------------------------\n";
if (memory.getThrds().size() > 0) {
for (int i = 0; i < memory.getThrds().size(); i++) {
Thrd thrd = memory.getThrds().get(i);
returnStr+=thrd.getId() + " \t" + thrd.getState() + "\t\t" + thrd.getHole().getHead() + "\t\t\t" + thrd.getHole().getSize()+"\n";
}
} else {
returnStr +="\t\t\t暂无进程!\n";
}
returnStr+="-------------------------------------------\n";
return returnStr;
}
(五)主要函数——delete()方法
删除的方法一共有两个,在这我写成了rm和rmdir,前者用来完成删除文件的操作,后者用于删除文件夹。
@Override
public Boolean rm(String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(file.exists()){
file.delete();
}else{
return false;
}
return true;
}
@Override
public Boolean rmdir(String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if (file.exists()) {
if (file.isDirectory()) {
return deleteDirectory(file);
} else {
return file.delete();
}
} else {
return false;
}
}
private boolean deleteDirectory(File directory) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file); // 递归删除子目录
} else {
file.delete();
}
}
}
return directory.delete(); // 删除当前目录
}
(六)主要函数——write()方法
write方法我一共设计了两种模式,一种是-a(append)追加模式,另一种是-c(cover)覆盖模式,都可以对文件做写入操作。具体代码如下。
@Override
public Boolean write(Integer mode,String filename, String content) {
content = content +"\n";
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
//0--追加,1--覆盖
if(mode == 0){
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file,true))) {
writer.write(content);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}else if(mode == 1){
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(content);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}}
return false;
}
三、程序流程设计等
1.首次适应算法
将空闲分区链以地址递增的顺序连接;在进行内存分配时,从链首开始顺序查找,直到找到一块分区的大小可以满足需求时,按照该作业的大小,从该分区中分配出内存,将剩下的空闲分区仍然链在空闲分区链中。
图 1 首次适应算法流程图
2.最佳分区分配算法
将空闲分区链中的空闲分区按照空闲分区由小到大的顺序排序,从而形成空闲分区链。每次从链首进行查找合适的空闲分区为作业分配内存,这样每次找到的空闲分区是和作业大小最接近的,所谓“最佳”。
图 2 最佳分区分配算法流程图
3.最坏分区分配算法
与最佳适应算法刚好相反,将空闲分区链的分区按照从大到小的顺序排序形成空闲分区链,每次查找时只要看第一个空闲分区是否满足即可。
图 3 最坏分区分配算法流程图
write方法流程图如下:
先读入语句,裁剪出用户输入的命令,由第一个字符,判断是什么命令。如果是write命令,那再判断语句格式是否正确,具体为:看是否是4个字符组成的语句。再然后,看write的模式,-a是追加模式,-c是覆盖模式。最后,第4个字符是要写入文件的内容。现在已经拿到了所有需要的参数,接下来根据参数,对文件进行插入操作就行了。另外,记得在结束的时候关闭文件。流程图如下所示。
图 4 write方法流程图
【源程序清单】
先说一下我的代码结构,如下:
框出来的源码在上面章节已经给出了,为了避免文档重复性,这一章只给FileManagerServiceImpl类和LinuxTerminalGUI类。如下:
1.FileManagerServiceImpl.java
package com.ly.service;
import com.ly.context.FileContext;
import com.ly.domain.Hole;
import com.ly.domain.Memory;
import com.ly.partition.Partition;
import jdk.nashorn.internal.parser.JSONParser;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.*;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @Author 林瑶
* @Description TODO
* @Date 2024/6/27 0:25
*/
public class FileManagerServiceImpl implements FileManagerService {
Partition partition = new Partition();
//初始化的内存大小
private final Integer memorySize = 1000;
//初始化一个内存
Memory memory = new Memory(memorySize);
//存储小内存块的链表
LinkedList<Hole> holeLinkedList = new LinkedList<>();
public FileManagerServiceImpl(){
File file = new File(FileContext.real);
if(!file.exists()){
file.mkdirs();
}
}
public Boolean login(String username, String password) {
checkUserDirectory();
Boolean flag = false;//默认无用户
try (BufferedReader br = new BufferedReader(new FileReader(FileContext.real + FileContext.now+"user.txt"))) {
String line;
while ((line = br.readLine()) != null) {
String[] parts = line.split(" ");
//如果查到有username,就更改标记
if(parts[0].equals(username)){
flag = true;
}
if (parts[0].equals(username) && parts[1].equals(password)) {
return true; // 登录成功
}
}
} catch (IOException e) {
e.printStackTrace();
}
return flag; // 登录失败
}
public Boolean register(String username, String password) {
try (FileWriter writer = new FileWriter(FileContext.real + FileContext.now+"user.txt", true)) {
writer.write(username + " " + password + "\n");
checkUserDirectory();
return true; // 注册成功
} catch (IOException e) {
e.printStackTrace();
}
return false; // 注册失败
}
@Override
public String ls() throws Exception{
File file = new File(FileContext.real + FileContext.now );
StringBuilder returnFileStr = new StringBuilder();
if(file.exists()){
for (String f : file.list()) {
returnFileStr.append(f).append(" ");
}
return returnFileStr.toString();
}else{
}
return "";
}
@Override
public Boolean attrib(String filename, String permission) throws IOException {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if (file.exists()) {
switch (permission) {
//设置只读
case "r":
Files.setAttribute(file.toPath(), "dos:readonly", true);
break;
//设置读写
case "w":
Files.setAttribute(file.toPath(), "dos:writeonly", false);
break;
default:
throw new IllegalArgumentException("Invalid permission value");
}
return true;
} else {
return false;
}
}
private String getFilePermission(File file) {
Path filePath = file.toPath();
String permissionStr = "";
//类型:文件or文件夹
if(file.isDirectory()){ permissionStr +="d"; }else{ permissionStr +="-"; }
//权限-读
if (Files.isReadable(filePath)) { permissionStr +="r"; }else{ permissionStr+="-"; }
//权限-写
if (Files.isWritable(filePath)) { permissionStr +="w"; }else{ permissionStr+="-"; }
//权限-执行
if (Files.isExecutable(filePath)) { permissionStr +="x"; }else{ permissionStr+="-"; }
return permissionStr;
}
public String ll() throws Exception {
File file = new File(FileContext.real + FileContext.now);
StringBuilder returnFileStr = new StringBuilder();
if (file.exists() && file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
returnFileStr.append("total ").append(files.length).append("\n");
for (File f : files) {
returnFileStr.append(formatFileDetails(f));
}
}
}
return returnFileStr.toString();
}
private String formatFileDetails(File f) {
String filePermission = getFilePermission(f);
String ownerName = "";
try {
ownerName = Files.getOwner(f.toPath()).getName();
} catch (IOException e) {
ownerName = "N/A";
}
long fileSize = f.length();
String formattedSize = String.valueOf(fileSize);
if (f.isDirectory()) {
formattedSize = getFolderSize(f)+"";
} else if (fileSize > 999) {
formattedSize = String.format("%.1f%s", fileSize / 1024.0, "K");
}
String creationTime = formatCreationTime(f.toPath());
String contentType = "";
if(f.isDirectory()){
contentType = "<DIR>";
}else if(f.isFile()){
contentType = "<FILE>";
}
String relativePath = f.getPath().length() > 30 ? "..." + f.getPath().substring(f.getPath().length() - 27) : f.getPath();
return String.format("%-10s\t%-15s\t%-2s\t%s\t%-2s\t%s\n", filePermission, ownerName, formattedSize, creationTime, contentType, relativePath);
}
//获取文件大小
private long getFolderSize(File folder) {
long length = 0;
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
length += file.length();
} else {
length += getFolderSize(file);
}
}
}
return length;
}
private String formatCreationTime(Path path) {
try {
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
FileTime creationTime = attributes.creationTime();
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm",Locale.ENGLISH);
return dateFormat.format(new Date(creationTime.toMillis()));
} catch (IOException e) {
return "N/A";
}
}
public String cd2(String arg) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + arg);
if("..".equals(arg)){
file.getParent().replace(FileContext.real,"");
}else if(file.exists() && file.isDirectory()){
FileContext.now = FileContext.now + arg;
}else{
System.out.println("cd: "+arg+": No such file or directory");
}
return FileContext.now;
}
@Override
public String cd(String arg) {
if ("../".equals(arg)) {
File parentDir = new File(FileContext.real + FileContext.now).getParentFile();
if (parentDir != null) {
FileContext.now = parentDir.getPath().substring(FileContext.real.length());
} else {
System.out.println("cd: " + arg + ": No such file or directory");
}
}else if("/".equals(arg)){
FileContext.now = "/";
}else if("".equals(arg.trim())){
FileContext.now = "";
} else {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + arg);
if (file.exists() && file.isDirectory()) {
FileContext.now = FileContext.now + arg;
} else {
System.out.println("cd: " + arg + ": No such file or directory");
}
}
return FileContext.now;
}
@Override
public Boolean cp(String filename, String newFilename) throws IOException {
File srcFile = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
File destFile = new File(FileContext.real + FileContext.now + File.separatorChar + newFilename);
// 如果源文件不存在
if(!srcFile.exists()){
throw new RuntimeException("找不到源文件");
}
// 如果目标文件不存在
if(!destFile.exists()){
destFile.mkdirs();
}
// 如果源文件是文件
if(srcFile.isFile()){
// 如果目标文件是文件
if(destFile.isFile()){
Files.copy(srcFile.toPath(),destFile.toPath());
}
// 如果目标文件是文件夹
else if(destFile.isDirectory()){
File newFile = new File(destFile,srcFile.getName());
Files.copy(srcFile.toPath(),newFile.toPath());
}
}
// 如果源文件是文件夹
else if(srcFile.isDirectory()){
if(destFile.isFile()){
throw new RuntimeException("源文件是文件夹,目标文件是文件,无法进行复制");
}
else if(destFile.isDirectory()){
File fs[] = srcFile.listFiles();
for(File f:fs){
File newFile = new File(destFile,f.getName());
// 如果子级文件是文件夹,则递归
if(f.isDirectory()){
cp(f.getAbsolutePath(),newFile.getAbsolutePath());
}
if(f.isFile()){
Files.copy(f.toPath(),newFile.toPath());
}
}
}
}
return true;
}
@Override
public Boolean mv(String filename, String newFilename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(file.exists()){
file.renameTo(new File(FileContext.real + FileContext.now + File.separatorChar + newFilename));
file.delete();
}else{
return false;
}
return true;
}
@Override
public Boolean mk(String filename) throws IOException {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(!file.exists()){
file.createNewFile();
}
return true;
}
@Override
public Boolean rm(String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(file.exists()){
file.delete();
}else{
return false;
}
return true;
}
@Override
public Boolean rmdir(String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if (file.exists()) {
if (file.isDirectory()) {
return deleteDirectory(file);
} else {
return file.delete();
}
} else {
return false;
}
}
private boolean deleteDirectory(File directory) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file); // 递归删除子目录
} else {
file.delete();
}
}
}
return directory.delete(); // 删除当前目录
}
@Override
public Boolean mkdir(String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(!file.exists()){
file.mkdirs();
}
return true;
}
@Override
public String cat(String filename) throws IOException {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
if(file.exists()){
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
}
return content.toString();
}
return null;
}
// write函数
@Override
public Boolean write(Integer mode,String filename, String content) {
content = content +"\n";
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
//0--追加,1--覆盖
if(mode == 0){
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file,true))) {
writer.write(content);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}else if(mode == 1){
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(content);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return false;
}
@Override
public String open(String algorithmId,String filename) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
//要申请的大小
long totalSpace = 0;
//申请的大小
if(file.isDirectory()){
totalSpace = getFolderSize(file);
}else{
totalSpace = file.length();
}
Hole hole = new Hole(file.getAbsolutePath().hashCode(), (int) file.length());
holeLinkedList.add(hole);
//1--首次适应算法
if("-ff".equals(algorithmId)){
memory = partition.FF(memory, (int) totalSpace);
//2--最佳适应算法
}else if("-zj".equals(algorithmId)){
memory = partition.ZJ(memory,(int) totalSpace);
//3--最坏适应算法
}else if("-zh".equals(algorithmId)){
memory = partition.ZH(memory,(int) totalSpace);
}
String showThrds = partition.showThrds(memory);
String showMemory = partition.showMemory(memory);
return showThrds+"\n"+showMemory;
}
@Override
public String close(Integer thrdId) {
memory = memory.releaseMemory(thrdId);
String showThrds = partition.showThrds(memory);
String showMemory = partition.showMemory(memory);
return showThrds+"\n"+showMemory;
}
@Override
public String head(String filename, Integer n) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
StringBuilder result = new StringBuilder();
if(file.exists()){
try {
FileReader fr = new FileReader(file);
BufferedReader bf = new BufferedReader(fr);
// 读取文件的前n行内容
String line;
int count = 0;
while ((line = bf.readLine()) != null && count < n) {
result.append(line).append("\n");
count++;
}
bf.close();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result.toString();
}
@Override
public String tail(String filename, Integer n) {
File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);
String result = "";
if(file.exists()){
try {
FileReader fr = new FileReader(file);
BufferedReader bf = new BufferedReader(fr);
// 读取文件的所有行
List<String> lines = new ArrayList<>();
String line;
while ((line = bf.readLine()) != null) {
lines.add(line);
}
bf.close();
fr.close();
// 输出倒数第n行到最后一行的内容
for (int i = Math.max(0, lines.size() - n); i < lines.size(); i++) {
result += lines.get(i) + "\n";
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
@Override
public String help(String command) {
if(command.equals("cd")){
return "cd directory1:进入名为directory1的文件夹中";
}else if(command.equals("ls")){
return "ls:列出该目录下所有文件名称";
}else if(command.equals("ll")){
return "ll:列出该目录下所有文件详细信息";
}else if(command.equals("cp")){
return "cp filename1 filename2:把filename1文件复制成filename2文件";
}else if(command.equals("mv")){
return "mv filename1 filename2:把filename1修改名称、移动为filename2";
}else if(command.equals("mkdir")){
return "mkdir directory1:创建名为directory1的文件夹";
}else if(command.equals("mk")){
return "mk filename1:创建名为filename1的文件";
}else if(command.equals("rmdir")){
return "rmdir directory1:删除名为directory1的文件夹";
}else if(command.equals("rm")){
return "rm filename1:删除名为filename1的文件";
}else if(command.equals("cat")){
return "cat filename1:展示filename1文件里的内容";
}else if(command.equals("head")){
return "head filename1 n:展示filename1文件里前n行的内容";
}else if(command.equals("tail")){
return "tail filename1 n:展示filename1文件里后n行的内容";
}else if(command.equals("attrib")){
return "attrib filename1 r:修改filename1文件为只读\n"+
"attrib filename1 w:修改filename1文件为读写";
}else if(command.equals("write")){
return "write filename1 context:把context写入文件filename1";
}else if(command.equals("open")){
return "open -ff filename1:打开文件f到内存中(最先适应存储分配算法)"+
"open -zj filename1:打开文件f到内存中(最佳适应存储分配算法)"+
"open -zh filename1:打开文件f到内存中(最坏适应存储分配算法)";
}else if(command.equals("close")){
return "close 进程id:关闭内存中进程号为id的进程";
}
return "这里是help方法";
}
@Override
public String help(){
String returnTxt =
"-----------------------------------------------------\n"+
" 功能目录 \n"+
"-----------------------------------------------------\n"+
"【cd f\t进入文件夹f中】\n"+
"【ls\t展示当前目录下文件名】\n"+
"【ll\t展示当前目录下文件物理地址】\n"+
"【cp f1 f2\t复制文件夹f1变成f2】\n"+
"【mv f1 f2\t移动文件夹f1变成f2】\n"+
"【mkdir f\t创建文件夹f】\n"+
"【mk f\t创建文件f】\n"+
"【rmdir f\t删除文件夹f】\n"+
"【rm f\t删除文件f】\n"+
"【cat f\t查看文件f的内容】\n"+
"【attrib f r\t修改文件f的权限为只读】\n"+
"【attrib f w\t修改文件f的权限为读写】\n"+
"【write -a f context\t把context内容写入文件f(追加)】\n"+
"【write -c f context\t把context内容写入文件f(覆盖)】\n"+
"【head f n\t读取文件f的前n行内容】\n"+
"【tail f n\t读取文件f的后n行内容】\n"+
"【open -ff f\t打开文件f到内存中(最先适应存储分配算法)】\n"+
"【open -zj f\t打开文件f到内存中(最佳适应存储分配算法)】\n"+
"【open -zh f\t打开文件f到内存中(最坏适应存储分配算法)】\n"+
"【close i\t释放内存中第i个线程】\n"+
"【clear\t清除屏幕】\n"+
"【exit\t退出系统】\n"+
"-----------------------------------------------------\n";
return returnTxt;
}
}
2.LinuxTerminalGUI.java
package com.ly;
import com.ly.context.FileContext;
import com.ly.service.FileManagerService;
import com.ly.service.FileManagerServiceImpl;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import lombok.SneakyThrows;
public class LinuxTerminalGUI extends Application {
FileManagerService fileManagerService;
private TextArea textArea;
private String lastCommand = "";
private String username;
private String password,password2;
private int lineIdx = 0;
//命令
private String cmd;
@Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("struggilr@"+System.getenv().get("COMPUTERNAME")+"~");
primaryStage.setWidth(1250);
primaryStage.setHeight(650);
fileManagerService = new FileManagerServiceImpl();
textArea = new TextArea();
textArea.setStyle("-fx-control-inner-background:#000000; -fx-text-fill: #FFFFFF; -fx-font-size: 18;");
// 监听文本区域的键盘按键事件
textArea.setOnKeyPressed(this::handleKeyPressed);
//模拟按下 Enter 键,从而触发 LoginF 方法
LoginF(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, false, false, false, false));
//检查用户目录,不存在就创建一下。
fileManagerService.checkUserDirectory();
// 创建一个栈面板,并将文本区域添加到栈面板中
StackPane root = new StackPane();
root.getChildren().add(textArea);
Scene scene = new Scene(root, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
@SneakyThrows
private void handleKeyPressed(KeyEvent event) {
//1--检查按下的键是否为字母键、数字键或空格键
if (event.getCode().isLetterKey() || event.getCode().isDigitKey() || event.getCode().isWhitespaceKey()) {
// 如果光标位置在命令行提示符后面,则禁止输入
if (textArea.getCaretPosition() < textArea.getText().lastIndexOf("$ ") + 2) {
event.consume(); // 禁止在命令行中输入
}
//2--检查按下的键是否为回车键
} else if (event.getCode() == KeyCode.ENTER) {
// 如果光标位置在命令行提示符后面或者在提示符上,则禁止换行
if (textArea.getCaretPosition() <= textArea.getText().lastIndexOf("$ ") + 2) {
event.consume(); // 禁止在命令行中换行
} else {
// 在文本区域末尾添加新的命令提示符并保存最后一条命令
// textArea.appendText("\n$ ");
textArea.insertText(textArea.getLength(), "\n"+command());
lastCommand = textArea.getText();
}
} //3--检查按下的键是否为退格键
else if (event.getCode() == KeyCode.BACK_SPACE && textArea.getCaretPosition() <= textArea.getText().lastIndexOf("$ ") + 2) {
event.consume(); // 禁止删除上一行命令
}
else if (event.getCode() == KeyCode.BACK_SPACE) {
// 如果光标位置在 "login as: " 之后,则正常处理退格键
if (textArea.getCaretPosition() > textArea.getText().indexOf("login as: ") + "login as: ".length()) {
// 允许删除光标位置之前的字符
return;
}
// 如果光标位置在 "login as: " 之前,则禁止删除
textArea.positionCaret(textArea.getText().indexOf("login as: ") + "login as: ".length()); // 设置光标位置在 "login as: " 之后
event.consume(); // 禁止删除 "login as: " 之前的文本
}
if (event.getCode() == KeyCode.ENTER) {
// 阻止默认的回车键行为
event.consume();
// 获取 "login as:" 后面换行之前的字符串
String inputCommand = textArea.getText();
lineIdx = textArea.getParagraphs().size();
System.out.println("【lineIdx:"+lineIdx+"】");
if(lineIdx==1&&inputCommand.startsWith("login as: ")){
username = inputCommand.substring("login as: ".length());
FileContext.user = username;
// 在文本区域末尾添加新的命令提示符并保存最后一条命令
textArea.insertText(textArea.getLength(), "\nlogin password: ");
}else if(lineIdx==2&&getLineText(inputCommand,lineIdx).startsWith("login password: ")){
password = getLineText(inputCommand,lineIdx).substring("login password: ".length());
if(fileManagerService.login(username,password)){
textArea.insertText(textArea.getLength(), "\n"+command());
}else{
textArea.insertText(textArea.getLength(), "\ncheck password: ");
}
}else if(lineIdx==3&&getLineText(inputCommand,lineIdx).startsWith("check password: ")){
password2 = getLineText(inputCommand,lineIdx).substring("check password: ".length());
if(password2.equals(password)){
Boolean registerStatus = fileManagerService.register(username, password);
if(registerStatus){
textArea.insertText(textArea.getLength(), "\n"+command());
}
}else{
textArea.insertText(textArea.getLength(), "\n两次输入密码不一致!check password: ");
}
}else{
if(inputCommand.length()>0&&lineIdx>0){
cmd = getLineText(inputCommand,lineIdx).substring(command().length());
}else{
cmd = "";
}
String[] args = cmd.split(" ");
if(cmd == null || "".equals(cmd)){
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(args.length>1 && "--help".equals(args[1])){
String helpTxt = fileManagerService.help(args[0]);
textArea.insertText(textArea.getLength(), "\n"+helpTxt);
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("cd")){
if(args.length>1){
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.cd(args[1]));
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cd"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if("ls".equals(cmd)){
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.ls());
textArea.insertText(textArea.getLength(), "\n"+command());
}else if("ll".equals(cmd)){
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.ll());
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("cp")){
if(args.length>2){
fileManagerService.cp(args[1],args[2]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cp"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("mv")){
if(args.length>2){
fileManagerService.mv(args[1],args[2]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mv"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("mkdir")){
if(args.length>1){
fileManagerService.mkdir(args[1]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mkdir"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("mk")){
if(args.length>1){
fileManagerService.mk(args[1]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mk"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("rmdir")){
Boolean rmdirStatus = false;
if(args.length>1) {
rmdirStatus = fileManagerService.rmdir(args[1]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("rmdir"));
}
System.out.println(rmdirStatus?"删除文件夹成功":"删除文件夹失败");
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("rm")){
Boolean rmStatus = false;
if(args.length>1) {
rmStatus = fileManagerService.rm(args[1]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("rm"));
}
System.out.println(rmStatus?"删除文件成功":"删除文件失败");
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("cat")){
String catRst = "";
if(args.length>1) {
catRst = fileManagerService.cat(args[1]);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cat"));
}
textArea.insertText(textArea.getLength(), "\n"+catRst);
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("head")){
String headTxt = "";
if(args.length>2){
int n = Integer.parseInt(args[2]);
headTxt = fileManagerService.head(args[1], n);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("head"));
}
textArea.insertText(textArea.getLength(), "\n"+headTxt);
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("tail")){
String tailTxt = "";
if(args.length>2){
int n = Integer.parseInt(args[2]);
tailTxt = fileManagerService.tail(args[1], n);
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("tail"));
}
textArea.insertText(textArea.getLength(), "\n"+tailTxt);
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("attrib")){
fileManagerService.attrib(args[1],args[2]);
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("write")){
if(args.length>3){
if("-a".equals(args[1])){
fileManagerService.write(0,args[2],args[3]);
}else if("-c".equals(args[1])){
fileManagerService.write(1,args[2],args[3]);
}
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("write"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("open")){
if(args.length>2){
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.open(args[1],args[2]));
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("open"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("close")){
if(args.length>1){
Integer thrdId = Integer.parseInt(args[1]);
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.close(thrdId));
}else{
textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("close"));
}
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("help")){
textArea.insertText(textArea.getLength(), "\n"+fileManagerService.help());
textArea.insertText(textArea.getLength(), "\n"+command());
}else if(cmd.startsWith("exit")){
System.exit(0);
}else if(cmd.startsWith("clear")){
textArea.setText(command());
textArea.positionCaret(textArea.getText().length());
event.consume();
}else{
// 在文本区域末尾添加新的命令提示符并保存最后一条命令
textArea.insertText(textArea.getLength(), "\n"+cmd+": command not found");
textArea.insertText(textArea.getLength(), "\n"+command());
}
}
// 检查用户输入的命令是否为 "exit",如果是则退出程序
if (inputCommand.trim().equalsIgnoreCase("exit")) {
System.exit(0); // 退出程序
}
}
}
public void LoginF(KeyEvent event){
// 设置文本的同时,把光标设置到末尾
textArea.appendText("login as: ");
textArea.positionCaret(textArea.getText().length());
event.consume();
}
//获取一行的输入(lineIdx从1开始)
public String getLineText(String text, int lineIdx) {
if (lineIdx < 1) {
return "err"; // 行索引必须从1开始
}
String[] lines = text.split("\n");
if (lineIdx > lines.length) {
return "err"; // 行索引超出范围
}
return lines[lineIdx - 1]; // 返回指定行的内容
}
private String command(){
return FileContext.user+"@"+System.getenv().get("COMPUTERNAME")+":"+fileManagerService.getNowPath()+"$ ";
}
private String missOpeErrMsg(String operandStr){
return operandStr+": missing operand\nTry '"+operandStr+" --help' for more information";
}
public static void main(String[] args) {
launch(args);
}
}
【测试结果】
【设计总结】
这个学期末的16-18周,一共是有1个大作业,2个课设,对我而言就相当于3个课设,其中,操作系统课设是我耗时最久完成的一个,共计用时两天两夜。
说实话写的挺崩溃的,一开始想内卷一下,所以就选择了用JavaGUI来复刻Linux终端,做出来黑框框还行,挺好做的,但真要写成linux那种的一样,可就有点麻烦了。除了得考虑每次换行的行首都是用户id和主机名,还得考虑光标位置在textArea的最后,以及不能让用户删除$ 之前的内容。由于我并没有系统学习过JavaGUI,所以前期屡屡碰壁,先是自己写了个lineIdx,用自增的方式来记录textArea中行数,结果发现报了好多错,找了好久才发现,呀,有内置函数直接或切换行数。所以就很顺利的把bug解决掉了。
第二个难点是我做完所有开发之后再开始做的附加题,这就导致我要改的代码量巨大无比,同时,还得高度专注,尽可能规避掉因为小错误导致的bug。再加上上学期学的东西基本都还给老师了,所以都还得重新学,从理清楚逻辑,到代码实现,还是有那么一丢丢难度的。
第三个难点本来不应该是登录注册的,但因为我凌晨写的时候逻辑有点乱套了,导致删删改改写了将近3个小时才完成。用户的账号密码用明文存储在txt里了,这种解决方案我是不很满意的,如果真认真写,除了加密以外,还可以加上转json操作、3次输密码机会的操作等等。另外还应该对登录失败的各种情况做判断,但实在没精力写了,这一块的工作就放掉了。
最后一个难点就是写文档了。还是有点小懒,没有在开发过程中就写好文档的习惯,于是,现在是早上7点,连续通了两天宵的我还在这码字,不过早上过去答个辩应该就能溜了吧,真得睡觉了。
总而言之,这次的操作系统课设还是非常有意思的,这也是我第一次独立完成操作系统的开发,虽然难点有好几个,但也基本是逐个攻破了。搜了很多资料,发现全网貌似没人做过类似的JavaGUI仿Linux终端,一方面觉得自己挺厉害的,还真让我做出来了,另一方面也知道,作为练习使用尚可,但真在实战中开发操作系统,用Java开发是绝对不行的,这也是我目前存在的问题之一,掌握的语言数量太少,没法灵活根据需求调节。
最后,感谢这10天里坚持不懈写了3个课设的自己,感觉自己进步非常大,也感谢老师的指导,期待大四还能有您的课!