Java 基础阶段 实战项目 学生成绩管理系统(控制台版)项目全篇注释,项目学习入门更快捷方便
·
功能清单
-
添加/删除学生
-
录入或修改某科成绩、删除某科成绩
-
查看所有学生(学号、姓名、科目数)
-
查看某学生成绩单 + 统计(总分/平均/最高/最低)
-
查看班级某科统计(平均、最高/最低及对应学生)
-
CSV 文件持久化(默认
grades.csv自动加载/保存)
# git 克隆地址
git clone https://gitcode.com/DeppBing/grade-manager.git

App
package com.example.grades;
import java.util.*;
public class App {
private static final Scanner SC = new Scanner(System.in); // 创建一个Scanner对象,用于从控制台读取输入
private static final GradeBook BOOK = new GradeBook(); // 创建一个GradeBook对象,用于存储学生信息
private static final CsvStorage STORAGE = new CsvStorage("grades.csv"); // 创建一个CsvStorage对象,用于保存和加载数据
/*
* 该文件主要执行一些 初始化操作
* */
public static void main(String[] args) { // 主线程类 用于 启动程序和执行选择操作功能
// Try auto-load if file exists
try {
int loaded = STORAGE.loadInto(BOOK); // 尝试从文件加载
if (loaded >= 0) { // 如果成功加载
System.out.println("已自动从 grades.csv 加载记录,共 " + loaded + " 条。");
}
} catch (Exception e) {
System.out.println("自动加载失败(可忽略): " + e.getMessage());
}
boolean running = true; // 控制循环的变量
while (running) {
printMenu(); // 显示菜单
String choice = prompt("请选择操作"); // 提示用户输入选项
switch (choice) { // 根据用户选择执行相应的操作
case "1" -> addStudent();
case "2" -> removeStudent();
case "3" -> addOrUpdateGrade();
case "4" -> removeGrade();
case "5" -> listStudents();
case "6" -> studentReport();
case "7" -> subjectStats();
case "8" -> save();
case "9" -> load();
case "0" -> { running = false; System.out.println("再见!"); }
default -> System.out.println("无效选项,请重试。");
}
}
}
private static void printMenu() { // 菜单
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. 查看某学生成绩单与统计");
System.out.println("7. 查看班级某科统计");
System.out.println("8. 保存到文件");
System.out.println("9. 从文件加载");
System.out.println("0. 退出");
}
private static String prompt(String msg) { // 提示用户输入
System.out.print(msg + ":");
/*
* trim() 方法 去除字符串两端的空白字符
* nextLine() 方法 读取一行输入信息
* */
return SC.nextLine().trim(); // 读取用户输入 并 返回
}
private static void addStudent() { // 添加学生
String id = prompt("学号");
if (id.isEmpty()) { System.out.println("学号不能为空"); return; }
String name = prompt("姓名");
if (name.isEmpty()) { System.out.println("姓名不能为空"); return; }
boolean ok = BOOK.addStudent(new Student(id, name));
System.out.println(ok ? "添加成功" : "学号已存在,添加失败");
}
private static void removeStudent() { // 删除学生
String id = prompt("要删除的学号");
boolean ok = BOOK.removeStudent(id);
System.out.println(ok ? "已删除" : "未找到该学号");
}
private static void addOrUpdateGrade() { // 录入/修改成绩
String id = prompt("学号");
Student s = BOOK.getStudent(id);
if (s == null) { System.out.println("未找到该学号"); return; }
String subject = prompt("科目名");
String scoreStr = prompt("成绩(0~100,可小数)");
try {
double score = Double.parseDouble(scoreStr);
if (score < 0 || score > 100) throw new IllegalArgumentException("成绩范围应在 0~100");
s.setGrade(subject, score);
System.out.println("录入/修改成功");
} catch (Exception e) {
System.out.println("成绩格式错误:" + e.getMessage());
}
}
private static void removeGrade() { // 删除某科成绩
String id = prompt("学号");
Student s = BOOK.getStudent(id); // 获取学生 getStudent 方法, 返回该学号的学生
if (s == null) { System.out.println("未找到该学号"); return; }
String subject = prompt("要删除的科目名");
boolean ok = s.removeGrade(subject);
System.out.println(ok ? "该科成绩已删除" : "未找到该科成绩");
}
private static void listStudents() { // 查看所有学生
List<Student> list = BOOK.listStudents();
if (list.isEmpty()) { System.out.println("暂无学生。"); return; }
System.out.println("学号\t姓名\t科目数");
for (Student s : list) {
System.out.printf("%s\t%s\t%d%n", s.getId(), s.getName(), s.getGrades().size());
}
}
private static void studentReport() { // 查看某学生成绩单与统计
String id = prompt("学号");
Student s = BOOK.getStudent(id);
if (s == null) { System.out.println("未找到该学号"); return; }
System.out.println("\n--- 成绩单 ---");
if (s.getGrades().isEmpty()) {
System.out.println("暂无成绩记录。");
} else {
System.out.println("科目\t成绩");
s.getGrades().forEach((sub, sc) -> System.out.printf("%s\t%.2f%n", sub, sc));
}
System.out.println("\n--- 统计 ---");
System.out.printf("总分:%.2f%n", s.total());
System.out.printf("平均分:%.2f%n", s.average());
System.out.printf("最高分:%s%n", s.maxEntry() == null ? "-" : s.maxEntry().getKey() + " " + String.format("%.2f", s.maxEntry().getValue()));
System.out.printf("最低分:%s%n", s.minEntry() == null ? "-" : s.minEntry().getKey() + " " + String.format("%.2f", s.minEntry().getValue()));
}
private static void subjectStats() { // 查看班级某科统计
String subject = prompt("科目名");
GradeBook.SubjectStats stats = BOOK.statsForSubject(subject);
if (stats.count == 0) {
System.out.println("该科目暂无成绩。");
return;
}
System.out.println("\n--- 班级《" + subject + "》统计 ---");
System.out.printf("人数:%d%n", stats.count);
System.out.printf("平均分:%.2f%n", stats.average);
System.out.printf("最高分:%.2f(%s)%n", stats.maxScore, stats.maxStudentName);
System.out.printf("最低分:%.2f(%s)%n", stats.minScore, stats.minStudentName);
}
private static void save() { // 保存到文件
try {
int n = STORAGE.saveFrom(BOOK);
System.out.println("已保存到 grades.csv,共写入记录:" + n);
} catch (Exception e) {
System.out.println("保存失败:" + e.getMessage());
}
}
private static void load() { // 从文件加载
try {
BOOK.clear();
int n = STORAGE.loadInto(BOOK);
System.out.println("已加载,共读取记录:" + n);
} catch (Exception e) {
System.out.println("加载失败:" + e.getMessage());
}
}
}
CsvStorage
package com.example.grades;
import java.io.*; // 导入io全部的包
import java.nio.charset.StandardCharsets; // nio.charset.StandardCharsets 方法用于获取 UTF-8 编码
import java.nio.file.*; // nio.file.Paths 方法用于获取 Path 对象
import java.util.*; // java.util 包含了util下的所有集合框架
/**
* 简单 CSV 存储:每行格式
* studentId,studentName,subject,score
* 当某学生暂无成绩时,subject 与 score 留空一行避免信息丢失。
*/
public class CsvStorage { // 存储器
private final Path path; // 文件路径 private 修饰符,用于创建一个只读的成员变量,确保成员变量的值不能被修改
public CsvStorage(String fileName) {
this.path = Paths.get(fileName);
} // 有参构造函数
public int saveFrom(GradeBook book) throws IOException { // 保存
List<String> lines = new ArrayList<>();
for (Student s : book.listStudents()) {
if (s.getGrades().isEmpty()) {
lines.add(escape(s.getId()) + "," + escape(s.getName()) + ",,");
} else {
for (Map.Entry<String, Double> e : s.getGrades().entrySet()) {
lines.add(escape(s.getId()) + "," + escape(s.getName()) + "," + escape(e.getKey()) + "," + e.getValue());
}
}
}
Files.write(path, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
return lines.size();
}
public int loadInto(GradeBook book) throws IOException { // 加载
if (!Files.exists(path)) return 0; // 文件不存在
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8); // 读取文件
Map<String, Student> temp = new LinkedHashMap<>(); // 临时存储 linkedHashMap 是一个有序的哈希表
/*
* for (String line : lines)
* 等同
* for (int i = 0; i < lines.size(); i++)
* */
for (String line : lines) {
if (line.isBlank()) continue;
String[] parts = splitCsv(line);
String id = parts.length > 0 ? unescape(parts[0]) : "";
String name = parts.length > 1 ? unescape(parts[1]) : "";
String subject = parts.length > 2 ? unescape(parts[2]) : "";
String scoreStr = parts.length > 3 ? parts[3] : "";
Student s = temp.computeIfAbsent(id, k -> new Student(id, name));
/*
* isEmpty() 方法判断字符串是否为空或长度为 0
* */
if (subject != null && !subject.isEmpty() && scoreStr != null && !scoreStr.isEmpty()) {
try {
double score = Double.parseDouble(scoreStr);
s.setGrade(subject, score);
} catch (NumberFormatException ignored) {}
}
}
book.clear();
/*
* for (Student s : temp.values()) book.addStudent(s);
* 等同于
* for ( int i = 0; i < temp.size(); i++) {
* book.addStudent(temp.get(i));
* }
* */
for (Student s : temp.values()) book.addStudent(s);
return lines.size();
}
private static String escape(String s) { // escape 字符串
if (s == null) return "";
/*
* contains() 方法用于判断字符串中是否包含指定的字符或字符串。
* replace() 方法用于将字符串中指定的子串替换为另一个子串。
* */
if (s.contains(",") || s.contains("\"") || s.contains("\n")) {
return "\"" + s.replace("\"", "\"\"") + "\"";
}
return s;
}
private static String unescape(String s) { //
s = s.trim();
if (s.startsWith("\"") && s.endsWith("\"") && s.length() >= 2) {
s = s.substring(1, s.length()-1).replace("\"\"", "\"");
}
return s;
}
private static String[] splitCsv(String line) { // 分割 CSV
List<String> parts = new ArrayList<>(); // 创建一个 ArrayList
StringBuilder cur = new StringBuilder(); // 创建一个 StringBuilder
boolean inQuotes = false; // 判断是否在引号中
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i); // charAt() 方法返回指定索引处的 char 值。
if (c == '\"') { // 判断是否在引号中
if (inQuotes && i+1 < line.length() && line.charAt(i+1) == '\"') { // 判断是否是转义字符
cur.append('\"'); // 添加转义字符
i++; // 跳过下一个字符
} else { // 否则,判断是否是引号
inQuotes = !inQuotes; // 切换引号状态
}
} else if (c == ',' && !inQuotes) { // 否则,判断是否是逗号
parts.add(cur.toString()); // 添加当前部分
cur.setLength(0); // 清空当前部分
} else { // 否则,添加字符
cur.append(c); // 添加字符
}
}
parts.add(cur.toString()); // 添加当前部分
return parts.toArray(new String[0]); // 转换为数组并返回
}
}
全部代码可点击链接下载,无需会员积分即可下载

更多推荐


所有评论(0)