
本教程详细讲解如何在 java 中读取文本文件,并解析其中的结构化数据(如学生姓名和成绩)以创建对应的 java 对象。文章对比了两种核心方法:使用 `scanner` 自定义分隔符和结合 `scanner` 逐行读取与 `string.split()`,并提供了详细的代码示例、注意事项以及如何将解析数据集成到自定义 `student` 类中。
在 Java 应用程序开发中,从外部文本文件读取并解析结构化数据是一个常见的需求。例如,我们可能需要从一个包含学生姓名和多门成绩的文本文件中提取信息,然后将其转化为 Java 对象,以便后续进行排序、分析或存储。本文将深入探讨两种主要的 Java 文件读取和数据解析策略,并指导您如何将这些数据封装到自定义的 Student 类中。
假设我们有一个名为 main.txt 的文本文件,其内容包含学生姓名和四门课程的成绩,数据之间以逗号和空格分隔,每行代表一个学生的信息:
John Doe, 30, 25, 70, 10 Jane Doe, 33, 20, 80, 15 Christian Pulisic, 70, 60, 50, 20
我们的目标是读取这些数据,将姓名作为字符串,成绩作为整数,并最终创建 Student 对象。
java.util.Scanner 类提供了一种方便的方式来解析基本类型和字符串。通过设置自定义分隔符,我们可以让 Scanner 自动按照特定模式分割输入流中的数据。
立即学习“Java免费学习笔记(深入)”;
此方法的核心在于 scan.useDelimiter()。我们需要定义一个正则表达式,它能匹配数据字段之间的分隔符以及行尾的换行符。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
// 假设 Student 类已定义
// class Student {
// String name;
// List<Integer> grades;
// public Student(String name, List<Integer> grades) {
// this.name = name;
// this.grades = grades;
// }
// @Override
// public String toString() {
// return "Name: " + name + ", Grades: " + grades;
// }
// }
public class StudentDataReaderDelimiter {
public static void main(String[] args) {
// 使用 try-with-resources 确保 Scanner 资源被正确关闭
try (Scanner scan = new Scanner(new File("main.txt"))) {
// 设置分隔符为换行符或 ", "
scan.useDelimiter("\R|, ");
List<Student> students = new ArrayList<>();
while (scan.hasNext()) {
try {
String name = scan.next();
List<Integer> grades = new ArrayList<>();
for (int i = 0; i < 4; i++) { // 假设有4门成绩
if (scan.hasNextInt()) {
grades.add(scan.nextInt());
} else {
System.err.println("Error: Expected an integer grade but found non-integer for student " + name);
// 跳过当前非整数 token,或者选择更严格的错误处理
scan.next(); // 尝试跳过错误的 token
break; // 停止读取当前学生的成绩
}
}
students.add(new Student(name, grades));
} catch (Exception e) {
System.err.println("Error processing data: " + e.getMessage());
// 可以在这里添加逻辑来跳过当前行或记录错误
// 注意:当使用 useDelimiter("\R|, ") 时,很难精确地跳过“当前行”
}
}
// 打印解析出的学生信息
for (Student student : students) {
System.out.println(student);
}
} catch (FileNotFoundException e) {
System.err.println("Error: File not found at specified path. " + e.getMessage());
}
}
}这种方法通常被认为是处理结构化文本文件更健壮和可控的方式。它将文件读取和行内解析分为两个独立步骤。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
// 假设 Student 类已定义
// class Student {
// String name;
// List<Integer> grades;
// public Student(String name, List<Integer> grades) {
// this.name = name;
// this.grades = grades;
// }
// @Override
// public String toString() {
// return "Name: " + name + ", Grades: " + grades;
// }
// }
public class StudentDataReaderSplit {
public static void main(String[] args) {
// 使用 try-with-resources 确保 Scanner 资源被正确关闭
try (Scanner scan = new Scanner(new File("main.txt"))) {
List<Student> students = new ArrayList<>();
int lineNumber = 0;
while (scan.hasNextLine()) {
lineNumber++;
String line = scan.nextLine(); // 读取整行
String[] tokens = line.split(", "); // 使用 ", " 分割行
// 验证 token 数量
if (tokens.length == 5) { // 1个姓名 + 4个成绩 = 5个 token
String name = tokens[0];
List<Integer> grades = new ArrayList<>();
boolean allGradesValid = true;
for (int i = 1; i < tokens.length; i++) {
try {
grades.add(Integer.parseInt(tokens[i]));
} catch (NumberFormatException e) {
System.err.println("Error on line " + lineNumber + ": Invalid grade format for '" + tokens[i] + "'");
allGradesValid = false;
break; // 跳出当前学生成绩的解析
}
}
if (allGradesValid) {
students.add(new Student(name, grades));
}
} else {
System.err.println("Error on line " + lineNumber + ": Incorrect number of data fields. Expected 5, found " + tokens.length + ". Line: '" + line + "'");
}
}
// 打印解析出的学生信息
for (Student student : students) {
System.out.println(student);
}
} catch (FileNotFoundException e) {
System.err.println("Error: File not found at specified path. " + e.getMessage());
}
}
}为了更好地组织和管理解析出的数据,我们应该定义一个 Student 类来封装学生姓名和成绩。
import java.util.List;
import java.util.ArrayList;
public class Student implements Comparable<Student> {
private String name;
private List<Integer> grades;
public Student(String name, List<Integer> grades) {
this.name = name;
this.grades = new ArrayList<>(grades); // 深度拷贝,防止外部修改
}
public String getName() {
return name;
}
public List<Integer> getGrades() {
return new ArrayList<>(grades); // 返回拷贝,防止外部修改
}
// 计算平均分(可选)
public double getAverageGrade() {
if (grades.isEmpty()) {
return 0.0;
}
int sum = 0;
for (int grade : grades) {
sum += grade;
}
return (double) sum / grades.size();
}
@Override
public String toString() {
return "Student [Name=" + name + ", Grades=" + grades + ", Average=" + String.format("%.2f", getAverageGrade()) + "]";
}
// 实现 Comparable 接口,用于按姓名字母顺序排序
@Override
public int compareTo(Student other) {
return this.name.compareTo(other.name);
}
}在上述的两个数据读取示例中,我们已经将解析出的姓名和成绩列表传递给了 Student 类的构造函数,并创建了 Student 对象。例如,在 StudentDataReaderSplit 类的 main 方法中:
// ... (之前的代码) ...
if (allGradesValid) {
students.add(new Student(name, grades)); // 创建 Student 对象并添加到列表中
}
// ... (之后的代码) ...将这些 Student 对象存储在一个 List<Student> 中,可以方便地进行后续操作,例如排序:
// 在解析完所有学生数据后
import java.util.Collections; // 导入 Collections 类
// ... (在 main 方法的最后) ...
Collections.sort(students); // 按姓名字母顺序排序学生列表
System.out.println("
--- Sorted Students ---");
for (Student student : students) {
System.out.println(student);
}try (Scanner scan = new Scanner(new File("main.txt"))) {
// 文件读取和解析逻辑
} catch (FileNotFoundException e) {
// 异常处理
}Scanner scan = new Scanner(new File("main.txt"), "UTF-8");在 Java 中读取文本文件并解析结构化数据,主要有两种高效且常用的方法:使用 Scanner 自定义分隔符和结合 Scanner 逐行读取与 String.split()。
无论选择哪种方法,都应遵循 Java 的最佳实践,包括正确的资源管理、健壮的异常处理和详尽的数据校验,以确保程序的稳定性和数据的准确性。通过将解析出的数据封装到如 Student 这样的自定义类中,可以极大地提高代码的可读性、可维护性和数据操作的便利性。
以上就是Java 文件读取与数据解析:从文本文件构建学生对象教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号