ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Java Swing+Txt学生信息管理系统:从文件存储到界面交互全解析

Java Swing+Txt学生信息管理系统:从文件存储到界面交互全解析 简介这是一份采用JavaSwing与TXT文件存储实现的学生信息管理系统源码包适合Java初学者完成课程设计或课堂实训重点解决图形界面下的学生信息管理需求。系统包含管理员登录模块教师可对学生信息进行增加、删除、修改、查询学生可查询个人资料角色划分清晰所有数据以TXT文件持久化保存界面友好操作简洁经多次测试运行稳定。压缩包共34个文件大小约62KB含9个Java源文件、20个class编译文件以及2个TXT数据文件和Eclipse工程配置文件源码、字节码与工程结构一应俱全便于导入IDE直接运行和二次修改。已有468人学习下载适合刚接触Swing组件、文件流读写和简单权限管理的开发者参考。资源整体结构紧凑运行入口明确稍加扩展即可接入数据库或增加分页、导出等功能是一份实用的入门练手项目。1. 学生信息管理系统为什么用 Swing Txt 就够Java Swing 加纯文本存储的组合放在今天看起来确实不那么“高级”但如果你要在一个学期内交出一份能跑、能改、能答辩的学生信息管理系统这个技术栈反而是最稳妥的选择。Swing 提供的是成熟的桌面控件不需要额外的前端工程链Txt 文件作为存储层省掉了数据库安装和 JDBC 配置让整个系统可以在任何装了 JDK 的机器上直接复制运行。更重要的是这种写法迫使你把文件读写、数据模型、界面刷新这些基础功全部暴露出来面试时被问到“Java 基础”和“java 面试八股文”里的 IO、集合、事件监听你都能找到实际代码对应。真正需要想清楚的不是“用什么技术”而是“数据格式怎么约定、界面怎么监听、文件读写怎么不出错”。2. 先定数据协议Txt 文件里的学生记录怎么设计2.1 字段定界与记录格式学生信息最少包含学号、姓名、年龄、班级这几个字段。Txt 文件里最常见的做法是每行一条学生记录字段之间用逗号或竖线分隔。逗号的问题在于某些文本字段里可能本身带逗号比如家庭住址。为了减少解析成本我一般建议用竖线|作为分隔符因为学生信息里几乎不会出现这个字符。2021001|张伟|20|计算机2101 2021002|李娜|21|计算机2102每条记录固定五个字段以内的场景下这种格式足够清晰。这里有一个隐含约定字段顺序一旦定了就不要改否则所有读写代码都要跟着动。如果后续要扩展电话、邮箱就在行尾追加字段读取时按索引取不要用“按列名”的方式去解析纯文本因为文本文件没有表头结构。2.2 读写工具类 StudentFileDAO数据访问对象DAO是这类项目里最值得认真写的一层。把文件路径、读逻辑、写逻辑封装在一个类里界面层只需要调用loadStudents()和saveStudents(ListStudent)不用关心文件在哪、编码是什么。2.2.1 读取方法public ListStudent loadStudents() { ListStudent list new ArrayList(); File file new File(FILE_PATH); if (!file.exists()) { return list; // 文件不存在时返回空列表而不是抛异常 } try (BufferedReader reader new BufferedReader( new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { String line; while ((line reader.readLine()) ! null) { if (line.trim().isEmpty()) { continue; // 跳过空行 } String[] parts line.split(\\|, -1); if (parts.length 4) { System.err.println(跳过格式错误的行: line); continue; } Student s new Student(parts[0], parts[1], Integer.parseInt(parts[2]), parts[3]); list.add(s); } } catch (IOException e) { e.printStackTrace(); } return list; }这段代码里有几个值得注意的参数选择。split(\\|, -1)中的-1表示保留末尾的空字符串防止某行数据少了字段却因为 split 默认丢弃尾部空串而被误判为格式正确。trim().isEmpty()判断用于过滤 Windows 下产生的\r残留行——虽然readLine()会去掉换行符但文件如果是 CRLF 保存的某些旧版 JDK 不会自动去掉\r空行判断就会失败。使用StandardCharsets.UTF_8是硬性要求后面会专门说乱码问题。2.2.2 写入与更新策略public boolean saveStudents(ListStudent students) { try (BufferedWriter writer new BufferedWriter( new OutputStreamWriter(new FileOutputStream(FILE_PATH), StandardCharsets.UTF_8))) { for (Student s : students) { writer.write(s.getId() | s.getName() | s.getAge() | s.getClassName()); writer.newLine(); } return true; } catch (IOException e) { e.printStackTrace(); return false; } }写入策略就是“全量覆盖”。界面层每次修改添加、删除、更新完列表后把整个 List 重新写回文件。这个策略对几百条学生记录完全够用写一次也就几毫秒。不要试图用 RandomAccessFile 去改中间的某一行纯文本文件的行长度不固定随机写会覆盖掉其他数据。全量写是这类系统最简单可靠的落盘方式也是面试官问“文件如何更新”时最容易接受的一种答案。2.3 编码与换行符的坑Txt 文件编码不统一是学生管理系统最常见的故障来源。Windows 记事本默认 ANSIGBK而 Java 源码里的字符串默认是 UTF-8如果读取时用FileReader跟随系统默认编码写入时又用 UTF-8就会出现中文乱码。解决方案是读写统一指定编码// 读取时用 UTF-8 new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8) // 写入时也用 UTF-8 new OutputStreamWriter(new FileOutputStream(FILE_PATH), StandardCharsets.UTF_8)但这样会遇到另一个问题用户双击用记事本打开生成的 txt 文件看到中文是乱码因为记事本默认按 GBK 解码。解决办法有两个一是写文件时加上 UTF-8 BOM 头\uFEFF记事本会识别为 UTF-8二是在系统说明里要求用户用 VS Code 或 Notepad 打开。我更推荐前者因为课程设计演示时老师大概率会用记事本直接点开数据文件。加 BOM 的写法if (file.length() 0) { writer.write(\uFEFF); }注意这个 BOM 只在第一次创建文件时写入否则每次追加都会多一个不可见字符。如果你不想处理 BOM也可以反过来读写都使用 GBK。但考虑到绝大多数开发环境默认 UTF-8统一用带 BOM 的 UTF-8 是更通用的方案。3. Swing 界面表格、表单和事件绑定的落地写法3.1 用 JTable DefaultTableModel 展示学生列表Swing 里展示多行数据最直接的控件是 JTable。不要直接在 JTable 上逐行设置值应该用DefaultTableModel管理数据JTable 只是模型的视图。模型里每一行是 Object 数组列名单独定义。DefaultTableModel tableModel new DefaultTableModel(); tableModel.addColumn(学号); tableModel.addColumn(姓名); tableModel.addColumn(年龄); tableModel.addColumn(班级); JTable table new JTable(tableModel); // 禁止直接编辑表格单元格 table.setDefaultEditor(Object.class, null);禁止编辑是个容易忽略的细节。如果不清除默认的编辑器用户双击单元格就能改但改完的数据没有同步回文件会造成界面和 txt 数据不一致。除非你专门实现了单元格编辑监听否则一律用setDefaultEditor(Object.class, null)关掉。3.1.1 从文件到表格模型的映射读取文件后把每个学生对象转换为 Object 数组逐行加到 TableModel 里private void refreshTable() { tableModel.setRowCount(0); // 先清空旧数据 for (Student s : dao.loadStudents()) { tableModel.addRow(new Object[]{s.getId(), s.getName(), s.getAge(), s.getClassName()}); } }注意setRowCount(0)比removeRow循环快得多它直接重置行数。每次增删改之后调用refreshTable()界面和数据文件就保持同步。这里有个小技巧loadStudents()每次都会重新读文件如果文件在外部被修改刷新时能看到最新数据这在演示时非常有用——你可以一边改 txt 一边点刷新按钮。3.2 表单区和按钮的事件处理界面上通常有“添加学生”“删除选中”“保存修改”三个按钮。添加时从文本框取值删除时从表格选中行取值。事件监听用 lambda 表达式最简洁。ListStudent students dao.loadStudents(); students.add(new Student(idField.getText(), nameField.getText(), Integer.parseInt(ageField.getText()), classField.getText())); boolean ok dao.saveStudents(students); if (ok) { refreshTable(); JOptionPane.showMessageDialog(this, 添加成功); } else { JOptionPane.showMessageDialog(this, 保存失败请检查文件权限); }Integer.parseInt没有做异常保护真实项目中应该在文本框失去焦点时校验或者用 try-catch 捕获 NumberFormatException。课程设计答辩时输入非数字年龄导致程序崩溃是很掉分的。正确的做法是在parseInt外层加校验int age -1; try { age Integer.parseInt(ageField.getText()); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, 年龄必须为数字); return; }3.2.1 删除选中行int row table.getSelectedRow(); if (row -1) { JOptionPane.showMessageDialog(this, 请先选择要删除的行); return; } String id (String) tableModel.getValueAt(row, 0); // 按学号定位而不是按行号 ListStudent students dao.loadStudents(); students.removeIf(s - s.getId().equals(id)); if (dao.saveStudents(students)) { refreshTable(); }这里的关键是“按学号定位删除”。因为loadStudents()读出来的列表顺序可能和当前表格顺序一致但一旦你对表格做过排序后面会讲行号就和文件顺序对不上了。所以必须用学号这个业务唯一键来做匹配而不是用 row 直接删列表对应位置。3.3 列表刷新与异常提示Swing 是单线程模型所有界面更新必须在「事件分发线程EDT」上执行。如果你自己 new 了一个线程去做文件读写写完之后直接操作组件大概率会抛出InterruptedException或者看到界面卡住不刷新。最简单的规避方式所有按钮的 actionPerformed 里不要启动新线程直接串行执行。文件操作只有几十毫秒用户感知不到卡顿。只有当数据量达到几万条时才需要考虑 SwingWorker几百条学生信息完全没必要。4. 查询与排序把文件中的学生数据玩出变化4.1 按学号/姓名模糊查询的实现查询不应该是“点按钮才查”而应该监听文本框的键盘事件实时过滤表格。searchField.getDocument().addDocumentListener(new DocumentListener() { Override public void insertUpdate(DocumentEvent e) { filter(); } Override public void removeUpdate(DocumentEvent e) { filter(); } Override public void changedUpdate(DocumentEvent e) { filter(); } }); private void filter() { String keyword searchField.getText().trim(); tableModel.setRowCount(0); ListStudent all dao.loadStudents(); for (Student s : all) { if (s.getName().toLowerCase().contains(keyword.toLowerCase()) || s.getId().contains(keyword)) { tableModel.addRow(new Object[]{s.getId(), s.getName(), s.getAge(), s.getClassName()}); } } }toLowerCase()是为了让查询不区分大小写。姓名和学号都用contains做模糊匹配而不是startsWith。如果你想同时支持班级查询加一个|| s.getClassName().contains(keyword)即可。注意这个过滤是临时性的dao.loadStudents()每次读取的都是文件里的全量数据不会因为过滤而丢失。4.2 对 JTable 做列排序JTable 自带的setAutoCreateRowSorter(true)是个好东西但它有个坑启用之后getSelectedRow()返回的是视图索引而不是模型索引。你如果直接用这个索引去tableModel.getValueAt(row, 0)拿到的可能是排序后的错误数据。table.setAutoCreateRowSorter(true); // 获取选中行时必须转换 int viewRow table.getSelectedRow(); if (viewRow -1) return; int modelRow table.convertRowIndexToModel(viewRow); String id (String) tableModel.getValueAt(modelRow, 0);这个转换是排序功能里最重要的一行代码。很多人在加了排序后删除行删错对象就是忘了convertRowIndexToModel。同时排序不会改变文件里的数据顺序因为表格模型里的数据没变只是视图重排了。删除时按模型索引取学号然后用学号去文件里删仍然安全。4.3 保存回 Txt 文件时的排序一致性如果你希望数据文件里也是排序后的顺序有两种做法一是直接按当前表格模型的顺序重写文件二是用Comparator对 List 排序再写。第二种更简洁因为 List 是内存对象排序后写入即可。students.sort(Comparator.comparing(Student::getId)); dao.saveStudents(students);Comparator.comparing要求属性是非空 String学号不会为空所以安全。如果按年龄排写Comparator.comparingInt(Student::getAge)避免自动装箱。这种排序在答辩时和表格排序配合使用能展示你对集合排序的理解。5. 交作业前值得做的三个加固技巧5.1 用临时文件保证原子写入直接把整个列表写到目标文件时如果程序中途崩溃或断电会留下一个半截文件下次读取时直接解析失败。常见做法是先写临时文件写入成功后再替换原文件。File tmp new File(FILE_PATH .tmp); try (BufferedWriter writer new BufferedWriter(new OutputStreamWriter( new FileOutputStream(tmp), StandardCharsets.UTF_8))) { // 写入所有行 } // 写入成功后再替换 File target new File(FILE_PATH); if (target.exists()) target.delete(); tmp.renameTo(target);renameTo在 Windows 下如果目标文件存在会失败所以先删除。严格来说这不算真正的原子操作但足够应付课程设计场景也让你的答辩多一个可说的点“我考虑了写入的完整性”。5.2 对中文乱码做自动探测如果你不知道数据文件是用 GBK 还是 UTF-8 保存的可以做一次简单探测读取前 N 个字节如果出现\uFEFFBOM就按 UTF-8否则尝试用 GBK 解码看是否乱码。最省事的办法是维护一个配置文件把编码写在里面。但更取巧的方式是public String detectEncoding(File file) throws IOException { try (InputStream in new FileInputStream(file)) { byte[] head in.readNBytes(3); if (head.length 3 head[0] (byte) 0xEF head[1] (byte) 0xBB head[2] (byte) 0xBF) { return UTF-8; } } return GBK; }这个技巧能解决“别人给的 txt 文件打开乱码”的问题。把探测到的编码传给InputStreamReader就兼容了两种常见保存方式。记得写入时固定用带 BOM 的 UTF-8这样探测结果永远是 UTF-8。5.3 把数据层和 UI 层分离便于口试答辩最后这个建议不是代码技巧而是答辩的救命稻草。很多学生的代码把文件读写直接写在按钮事件里老师一追问“如果你要改用 MySQL怎么改”当场就卡住。正确的结构是interface StudentDao { ListStudent loadStudents(); boolean saveStudents(ListStudent students); } class StudentFileDao implements StudentDao { // 文件实现 } class StudentPanel extends JPanel { private StudentDao dao; // 只和 dao 交互不直接 new File }这样改数据库时只需要新增一个StudentMysqlDao界面层一行不用动。答辩时这句话说出来比多做两个功能都加分。同时这也符合 java 面试题里常考的“面向接口编程”和“开闭原则”。把这个三层结构理清楚你的 Swing 学生管理系统就不再只是“拖控件”的项目而是有设计感的完整小系统了。本文还有配套的精品资源点击获取
返回列表