基于JavaGUI的简单Linux操作系统

【设计题目】

二级文件系统设计

 

【设计目的】

(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.首次适应算法

        将空闲分区链以地址递增的顺序连接;在进行内存分配时,从链首开始顺序查找,直到找到一块分区的大小可以满足需求时,按照该作业的大小,从该分区中分配出内存,将剩下的空闲分区仍然链在空闲分区链中。

6e6ca4447dd646ae8f32f8d03749ab8b.png

图 1 首次适应算法流程图

2.最佳分区分配算法

        将空闲分区链中的空闲分区按照空闲分区由小到大的顺序排序,从而形成空闲分区链。每次从链首进行查找合适的空闲分区为作业分配内存,这样每次找到的空闲分区是和作业大小最接近的,所谓“最佳”。

b1b93ed3474740098098b33a37f35942.png

图 2 最佳分区分配算法流程图

3.最坏分区分配算法

        与最佳适应算法刚好相反,将空闲分区链的分区按照从大到小的顺序排序形成空闲分区链,每次查找时只要看第一个空闲分区是否满足即可。

042f8e29d6fb4b1b9a309c7f776d8c31.png

图 3 最坏分区分配算法流程图


write方法流程图如下:

         先读入语句,裁剪出用户输入的命令,由第一个字符,判断是什么命令。如果是write命令,那再判断语句格式是否正确,具体为:看是否是4个字符组成的语句。再然后,看write的模式,-a是追加模式,-c是覆盖模式。最后,第4个字符是要写入文件的内容。现在已经拿到了所有需要的参数,接下来根据参数,对文件进行插入操作就行了。另外,记得在结束的时候关闭文件。流程图如下所示。

583c353acc5242f283d6604ca537a7a0.png

图 4 write方法流程图

【源程序清单】

先说一下我的代码结构,如下:

73ba5adf42324046a7087fd97c707f7f.png

框出来的源码在上面章节已经给出了,为了避免文档重复性,这一章只给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);
    }
}

【测试结果】

f5e41730d4c14a26bd659d901e7c7e16.png

5a531c80393442d3b20e956a46151f2c.png

38ae895dd6ff4da99a37d9163f8b9351.png

c0d18376bed44f7caaf5e6dffa9fa347.png

1a6c69cce4164c3f996cf9e7b1ba2f57.png

0e4a7fa83a6a433b85518439bdec847d.png

5eba2083d93c4fbba637e78e843da2b3.png

40459692788f4241bd7f2649443b9cb8.png

9836d84d304b429c87934b4d3c1cd84d.png

【设计总结】

        这个学期末的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个课设的自己,感觉自己进步非常大,也感谢老师的指导,期待大四还能有您的课!

 

 

 

 

 

 

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深山老林.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值