# 实验4 学生选课模拟系统之文件输入输出 **Repository Path**: night-yang/homework4 ## Basic Information - **Project Name**: 实验4 学生选课模拟系统之文件输入输出 - **Description**: 实验4学生选课模拟系统之文件输入输出 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-11-01 - **Last Updated**: 2023-11-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 实验4 学生选课模拟系统之文件输入输出 #### 实验目的 掌握文件输入输出; 了解对象序列化方法。 #### 业务要求 在实验三(学生选课模拟系统)的基础上,利用文件保存选课结果,实现方法以下 三选一: 1. 采用自定义格式化方法写入文本文件,并从该文件中读取及解析。 2. Excel 文件,基于 POI 或者 JXL 类库 3. 对象序列化 1)采用对象序列化的 writeObject 方法把选课结果存到硬盘文件系统中; 2)采用对象序列化的 readObject 方法从文件中恢复对象,并操作学生的选课课 表,实现退课操作。 3)打印课程对象信息,采用覆盖定义 toString()方法的方式。 #### 解题思路 1. 按特定格式写入文本再读取解析的方式太原始了,是以前在c语言中我常用的方式。受结课实验项目的启发,我想使用将对象序列化的方式实现存储。 2. 纵观代码,其实想要实现存储与载入,所有对象数据都已经集合在 **HashMap courses; HashMap students;** 两个HashMap之中,我们直接将两个HashMap存储即可。为了减少writeObject和readObject的方法数,不妨大胆一点,直接把两个HashMap取消static修饰,作为Main的一部分,然后存储和载入只需要读写一个Main类型的对象即可。程序开始运行时,检查是否存在存档文件,没有则新建Main的实例化对象,有则尝试载入Main。 3. 我们可以在MyFrame类中重载Jframe的dispose方法,在每次窗口销毁时writeObject存档。 #### 修改步骤 1. 既然说是通过对象序列化的方法读写内容。自然要先 **序列化** ,虽然在代码中我们只需要存一个Main类型的对象,但Main类中涉及的每个其它类型的对象也都要声明**implements Serializable** ,之后我们要在Main类中声明一个变量`private static final long serialVersionUID=67833L;`67833是自定义的一个版本号,用于确定存档文件结构是否和当前代码结构一致,每次修改代码时改变,虽然这行代码不加一般也不会出问题,因为程序会在编译时自己生成一个版本号,但自动生成的并不100%靠谱,一些代码上的微调不一定会让版本号自动改变,导致运行时报错。 2. 在之前的代码中,两个HashMap都是由static修饰的,现在我们要删掉static并解决由此产生的后果,因为要实例化Main了,我们正好给Main写个空参构造器。 ``` public Main() { courses=new HashMap<>(); students=new HashMap<>(); } ``` **从文件载入序列化的对象并不会重新执行一遍构造方法,而是完全像是继续一个被暂停的程序,无需担心这样载入存档时会导致HashMap被初始化。** 接下来我们要把所有地方对两个HashMap的调用由之前的Main.静态调用方式改成对象的,如何保证它们调用的都是同一个实例化的Main对象呢?我们给MyFrame类的构造方法添加一个参数,传入在Main中实例化的Main对象 ``` Main main=loadMain(); new MyFrame("实验二 学生模拟选课系统v0.4",1,main); ``` loadMain()方法的详细代码将在下个模块展示。 同样的,Button类中的方法也传入Main类型的同一对象即可。 3. 最后,我们在MyFrame类中重载dispose方法,代码中所有其他位置的dispose都改为调用此方法,传入Main类型的对象,调用save方法后再调用原本的dispose方法。 4. 测试一番,没有任何问题,所有内容均可正常被保存和载入。 #### 关键代码 1. loadMain方法,将对象从根目录‘HomeWork4_Data’文件恢复,如果出现版本号不同或文件不存在等异常问题则直接返回一个新的Main类型对象。 ``` public static Main loadMain(){ Main main = new Main(); try { FileInputStream fis = new FileInputStream("HomeWork4_Data"); ObjectInputStream ois = new ObjectInputStream(fis); main = (Main) ois.readObject(); ois.close(); fis.close(); } catch (IOException | ClassNotFoundException ioe) { System.out.println(ioe.getMessage()); } return main; } ``` 2. saveMain方法,将传入的Main对象存储至‘HomeWork4_Data’ ``` public static void saveMain(Main main){ try { FileOutputStream fos = new FileOutputStream("HomeWork4_Data"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(main); oos.close(); fos.close(); } catch (IOException ioe) { System.out.println("saveMain:"+ioe.getMessage()); } } ``` 3. 懒得给每个确定按钮都加上save方法所以干脆直接重载的dispose方法 ``` public void dispose(Main main2){ Main.saveMain(main2); System.out.println("save"); super.dispose(); } ``` 4.最后虽然GUI界面用不上toString方法,但是既然实验要求里提到了,那就给Crouse类重写一个toString方法放在下面。 ``` @Override public String toString() { return " 课程id:"+id+" 课程名称:"+name+" 课程地点:"+place+" 课程时间:"+time+" 课程教师:"+teacher.name; } ``` 运行输出示例: ``` 课程id:2002 课程名称:Java 课程地点:312 课程时间:9:00 课程教师:王老师, 230= 课程id:230 课程名称:Java 课程地点:313 课程时间:9:00 课程教师:王老师 ``` #### 实验感想 1. 了解并掌握了对象序列化方法、文件的输入输出。 2. 对‘流’有了更好的理解,知道了FileInputStream和ObjectInputStream以及Buffer的使用场景。 3. 这种方法能够十分简单便捷的实现程序的储存和恢复,但是感觉会带来一些严重的安全性问题,使得程序更易于被分析,如果是要发布出去的不想开源的软件,我不会使用本方案。