题目要求
改造学生管理系统或购物车系统,添加基于文本文件与基于二进制文件的存储系统。
这里我是以之前学生信息管理系统博客里的代码为基础进行的改造。
改动点
(1)创建 BinaryFileStudentDAO.java 文件
层级结构如下:

代码如下:
java
package dao.impl;
import dao.StudentDAO;
import entity.Student;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class BinaryFileStudentDAO implements StudentDAO {
private static final String FILE_PATH = "students.dat"; // 二进制存储文件
@Override
public void addStudent(Student student) {
List<Student> students = getStudents();
students.add(student);
saveStudents(students);
}
@Override
public boolean removeStudent(String id) {
List<Student> students = getStudents();
boolean removed = students.removeIf(student -> student.getId().equals(id));
if (removed) saveStudents(students);
return removed;
}
@Override
public List<Student> getStudents() {
File file = new File(FILE_PATH);
if (!file.exists()) return new ArrayList<>();
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
return (List<Student>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return new ArrayList<>();
}
}
// 序列化保存学生列表
private void saveStudents(List<Student> students) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH))) {
oos.writeObject(students);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public List<Student> searchByName(String name) {
List<Student> result = new ArrayList<>();
for (Student s : getStudents()) {
if (s.getName().contains(name)) result.add(s);
}
return result;
}
@Override
public List<Student> searchByMajor(String major) {
List<Student> result = new ArrayList<>();
for (Student s : getStudents()) {
if (s.getMajor().contains(major)) result.add(s);
}
return result;
}
@Override
public List<Student> searchByGpa(double gpa) {
List<Student> result = new ArrayList<>();
for (Student s : getStudents()) {
if (Math.abs(s.getGpa() - gpa) < 0.001) result.add(s);
}
return result;
}
}
(2)修改 Student.java 文件
主要是新增这一条语句:
java
private static final long serialVersionUID = 1L;
完整代码如下:
java
package entity;
import java.io.Serializable; // 导入序列化接口
public class Student implements Serializable { // 实现接口
private static final long serialVersionUID = 1L; // 序列化版本号(新增)
private String id;
private String name;
private String major;
private double gpa;
private int age;
private String gender;
public Student(String id, String name, String major, double gpa, int age, String gender) {
this.id = id;
this.name = name;
this.major = major;
this.gpa = gpa;
this.age = age;
this.gender = gender;
}
public Student() {}
public Student(String id, String name, String major, double gpa) {
this(id, name, major, gpa, 0, "未知");
}
@Override
public String toString() {
return "学号:" + id + ",姓名:" + name + ",专业:" + major + ",GPA:" + gpa + ",年龄:" + age + ",性别:" + gender;
}
// 所有getter/setter保持不变
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getMajor() { return major; }
public void setMajor(String major) { this.major = major; }
public double getGpa() { return gpa; }
public void setGpa(double gpa) { this.gpa = gpa; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
}
(3)修改 Main.java 文件
主要是把二进制的 DAO 文件导入。
代码如下:
java
package code;
import dao.StudentDAO;
import dao.impl.ListStudentDAO;
import dao.impl.TextFileStudentDAO;
import dao.impl.ExcelStudentDAO;
import dao.impl.BinaryFileStudentDAO; // 导入二进制DAO
import entity.Student;
import service.StudentService;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
StudentService service = null;
System.out.println("选择存储模式:1. List 存储 2. 文本文件存储 3. Excel 存储 4. 二进制文件存储");
int choice = scanner.nextInt();
scanner.nextLine();
if (choice == 1) {
StudentDAO dao = new ListStudentDAO();
service = new StudentService(dao);
} else if (choice == 2) {
StudentDAO dao = new TextFileStudentDAO();
service = new StudentService(dao);
} else if (choice == 3) {
StudentDAO dao = new ExcelStudentDAO();
service = new StudentService(dao);
} else if (choice == 4) {
StudentDAO dao = new BinaryFileStudentDAO();
service = new StudentService(dao);
} else {
System.out.println("无效选择,退出程序");
return;
}
boolean running = true;
while (running) {
System.out.println("\n学生信息管理系统");
System.out.println("1. 添加学生");
System.out.println("2. 删除学生");
System.out.println("3. 查看所有学生");
System.out.println("4. 按姓名搜索学生");
System.out.println("5. 按专业搜索学生");
System.out.println("6. 按 GPA 搜索学生");
System.out.println("7. 退出");
System.out.print("请选择操作:");
int operation = scanner.nextInt();
scanner.nextLine();
switch (operation) {
case 1:
Student student = new Student();
System.out.print("输入学号:");
student.setId(scanner.nextLine());
System.out.print("输入姓名:");
student.setName(scanner.nextLine());
System.out.print("输入专业:");
student.setMajor(scanner.nextLine());
System.out.print("输入 GPA:");
student.setGpa(scanner.nextDouble());
scanner.nextLine(); // 吸收换行符
System.out.print("输入年龄:");
student.setAge(scanner.nextInt());
scanner.nextLine(); // 吸收换行符
System.out.print("输入性别:");
student.setGender(scanner.nextLine());
service.addStudent(student);
System.out.println("学生添加成功");
break;
case 2:
System.out.print("输入要删除的学生学号:");
String id = scanner.nextLine();
if (service.removeStudent(id)) {
System.out.println("学生删除成功");
} else {
System.out.println("未找到该学号的学生");
}
break;
case 3:
List<Student> allStudents = service.getStudents();
if (allStudents.isEmpty()) {
System.out.println("暂无学生信息");
} else {
for (Student s : allStudents) {
System.out.println(s);
}
}
break;
case 4:
System.out.print("输入要搜索的学生姓名:");
String name = scanner.nextLine();
List<Student> nameStudents = service.searchByName(name);
if (nameStudents.isEmpty()) {
System.out.println("未找到该姓名的学生");
} else {
for (Student s : nameStudents) {
System.out.println(s);
}
}
break;
case 5:
System.out.print("输入要搜索的专业:");
String major = scanner.nextLine();
List<Student> majorStudents = service.searchByMajor(major);
if (majorStudents.isEmpty()) {
System.out.println("未找到该专业的学生");
} else {
for (Student s : majorStudents) {
System.out.println(s);
}
}
break;
case 6:
System.out.print("输入要搜索的 GPA:");
double gpa = scanner.nextDouble();
scanner.nextLine();
List<Student> gpaStudents = service.searchByGpa(gpa);
if (gpaStudents.isEmpty()) {
System.out.println("未找到该 GPA 的学生");
} else {
for (Student s : gpaStudents) {
System.out.println(s);
}
}
break;
case 7:
running = false;
System.out.println("程序已退出");
break;
default:
System.out.println("无效操作,请重新选择");
}
}
scanner.close();
}
}
运行结果


大家会发现我们输出的 student.dat 文件出现了乱码,这是因为 students.dat 是二进制序列化文件,并非文本文件,用文本编辑器会把二进制数据按照字符编码(如 UTF-8)去解析,而序列化的二进制数据并不是合法的文本字符,因此会显示为乱码。
那如何解决这个问题呢?这里我们推荐一个软件 notepad-- ,我们用它来打开我们的 student.dat 文件,再点击菜单-编辑-以二进制模式打开:

这下就能看到清晰的十六进制字符串了。

问题回答
能否理解 Student 类中需要加入 serialVersionUID 属性?
首先说说 serialVersionUID 的作用:
serialVersionUID是序列化机制用于版本控制的标识,它的主要作用是:
- 保证版本一致性:当类实现 Serializable 接口且未显式定义 serialVersionUID 时,Java 会自动生成一个(基于类的字段、方法等结构)。
- 避免反序列化异常:如果序列化后类结构发生变化(如增减字段、修改方法),自动生成的 serialVersionUID 会改变。此时用旧版本字节流反序列化新版本类,会抛出 InvalidClassException。
- 支持兼容性修改:显式定义 serialVersionUID 后,即使类结构有小变动(如新增字段),只要版本号不变,反序列化仍能兼容旧版本字节流。
那为什么 Student 类需要它呢?
因为在 BinaryFileStudentDAO 中,通过对象序列化读写学生数据:
java
// BinaryFileStudentDAO中序列化学生列表
private void saveStudents(List<Student> students) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH))) {
oos.writeObject(students);
} catch (IOException e) {
e.printStackTrace();
}
}
这段代码中,students 列表被序列化到文件,而列表中的元素 Student 必须实现 Serializable 接口。此时如果不显式定义 serialVersionUID,当 Student 类结构变化时,自动生成的 serialVersionUID 会改变,导致旧文件中的数据无法反序列化。而显式定义后,只要版本号不变,就能保证新旧版本的兼容性。
今天的分享就到这里。