Reading/splitting Java 中文本文件中不同行的不同部分

Reading/splitting different parts from different lines in a text file in Java

我正在做这个作业,我应该从这样的文本文件中读取...

Student Name: John
Student ID: 12344/19
College: Science

Credits Attempted: 15
Credits Earned: 15
Grade Points: 41.2
Course Code Course Title            Credit      Grade
COMP1007,   Amazing Applications of AI,  2,      B
COMP2202,   Fund. of Object Oriented Prog.,  3,      C-
MATH2108,   Calculus (2),            3,          C-
MATH3340,   Discrete Math. for Comp. Sci.,   3,          B-
STAT2101,   Introduction to Statistics,      4,          C+

我应该阅读此文本文件并计算学生的 GPA 并创建一个应该如下所示的输出文件...

Output text file

所以基本上我被困住了,我不知道我该做什么... 我知道如何逐行阅读并将一行分成不同的部分,但这似乎在这里不起作用,因为每一行都与另一行不同。例如,第一行有两部分,“学生姓名”和姓名本身,在本例中为“John”。但是在第 9 行,有四个不同的部分,课程代码、课程名称、学分和成绩。

老实说,我不想在作业上作弊,只是想理解它

帮助:)

注意我不能使用 Stream 或 Hashmap 或 BufferedReader

好的,这就是您的操作方式...

  1. 您读入所有文件并将每一行存储在 List<String>
  2. 对于前 8 行,您以单独的方式处理每一行。您甚至可以编写一个单独的函数来解析行 0-7
  3. 的每一行中的必要信息
  4. 其余所有行都具有相同的结构。因此,您可以将它们全部以相同的方式处理以解析出来,然后处理必要的数据。

如果有什么不清楚的地方,请评论这个答案,我会澄清的。

文本文件中的每条数据记录总是有一个开始和一个结束。最简单的记录显然是那些包含在文本文件中的单个分隔行中的记录,其中每个文件行实际上是一条记录,正如您在典型的 CSV format 数据文件中看到的那样。更难读取的记录是多行记录,而每个数据记录由几个连续的文本文件行组成,但仍然有一个 Start 和一个 End到每条记录。

记录的开始通常很容易区分。例如,在您在 post 中提供的文件示例中,显然是以 Student Name:.

开头的任何文件行

记录的结束 可能并不总是那么容易确定,因为许多应用程序不保存不包含数据值的字段为了帮助提高访问速度和减少文件膨胀。这个想法是“为什么要有一个充满空白字段的文本文件”,老实说,这是正确的。无论如何,我不是文本文件记录的忠实拥护者,因为使用数据库对于 更大的 数据量更有意义。在任何情况下,总会有一个文件行指示记录的 Start,因此从 Start 读取到 开始下一条记录或者如果是文件中的最后条记录,从开始文件结束(EOF)。

这是一个例子(阅读代码中的注释):

// System line separator to use in files.
String ls = System.lineSeparator();   
/* Array will hold student data: Student Name, Student ID, College,
   Credits Attempted, Credits Earned, and finally Grade Points.  */
String[] studentData = new String[6]; 
// String Array to hold Course Table Header Names.
String[] coursesHeader = {"COURSE NO", "COURSE TITLE", "CREDITS", "GRADE"};
// List Interface to hold all the course Data line Arrays for each record
java.util.List<String[]> cousesList = new java.util.ArrayList<>();
// Underlines to be used for Console display and file records
// Under courses Header
String underline1 = "-------------------------------------------------------------";
// Under all the courses
String underline2 = "------------------------------------------------------------------------------------";
    
/* Read and Write to files using 'Try With Resources' so to 
   automatically close the reader an writer objects.     */
try (Scanner reader = new Scanner(new java.io.File("StudentData.txt"), "UTF-8"); 
            java.io.Writer writer = new java.io.FileWriter("StudentsGPA.txt")) {
    // For console display only! [Anything (except caught errors) to Console can be deleted]
    System.out.println("The 'StudentsGPA.txt' file will contain:");
    System.out.println("======================================");
    System.out.println();
        
    // Will hold each line read from the reader
    String line = "";
    /* Will hold the name for the next record. This would be the record START
       but only AFTER the first record has been read.          */
    String newName = "";
        
    // Start reading the 'StudentData.txt' file (line by line)...
    while (reader.hasNextLine()) {
        /* If newName is empty then we're on our first record or 
           there is only one record in file.             */
        if (newName.isEmpty()) {
            line = reader.nextLine();   // read in a file line...
        }
        else {
            /* newName contains a name so we must have bumped into
               the START of a new record during processing of the 
               previous record. We aleady now have the first line 
               of this new record (which is the student's name line) 
               currently held in the 'newName' variable so we just 
               make 'line' equal what is in the 'newName' variable
               and carry on processing the data as normal. in essance,
               we simply skipped a read because we've already read it 
               earlier when processing the previous record.    */
            line = newName;
            // Clear this variable in preparation for another record START.
            newName = "";  
        }
        /* Skip comment lines (lines that start with a semicolon (;)
           or a hash mark (#). Also skip any blank lines.            */
        if (line.startsWith(";") || line.startsWith("#") || line.isEmpty()) {
            continue;
        }
        /* Does this file line start with 'Student Name:'? If so then 
           this is a record START, let's process this record. If not 
           then just keep reading the file.            */
        if (line.startsWith("Student Name:")) {
            /* Let's put the student name into the studentData array at 
               index 0. If it is detected that there has been no name
               applied for some reason then we place "N/A" as the name.
               We use a Ternary Operator for this. So, "N/A" will be a 
               Default if there is not name. This will be typical for 
               the other portions of student data.                */
            studentData[0] = line.isEmpty() ? "N/A" : line.split("\s*:\s*")[1].trim();
            /* Let's keep reading the file from this point on and retrieve 
               the other bits of student data to fill the studentData[] 
               Array...                    */
            for (int i = 1; i < 6; i++) {
                line = reader.nextLine().trim();
                /* If we encounter a comment line or a blank line then let's
                   just skip past it. We don't want these.               */
                if (line.startsWith(";") || line.startsWith("#") || line.isEmpty()) {
                    i--;
                    continue;
                }
                /* Store the other portions of student data into the 
                   studentData Array using "N/A" as a default should
                   any student data field contain nothing.        */
                studentData[i] = line.isEmpty() ? "N/A" : line.split("\s*:\s*")[1].trim();
            }
            // The current Student's Courses...
            /* Clear the List Interface object in preparation for new 
               Courses from this particular record.               */
            cousesList.clear();
            // Read past the courses header line...We don't want it.
            reader.nextLine();
            // Get the courses data (line by line)...
            while (reader.hasNextLine()) {
                line = reader.nextLine().trim();
                /* Again, if we encounter a comment line or a blank line 
                   in this section then let's just skip past it. We don't 
                   want these. */
                if (line.startsWith(";") || line.startsWith("#") || line.isEmpty()) {
                    continue;
                }
                /* At this point, if we have read in a line that starts
                   with 'Student Name:' then we just hit the START of a 
                   NEW Record! This then means that the record we're 
                   currently working on is now finished. Let's store this 
                   file line into the 'newRecord' variable and then break 
                   out of this current record read.                     */
                if (line.startsWith("Student Name:")) {
                    newName = line;
                    break;
                }
                /* Well, we haven't reached the START of a New Record yet
                   so let's keep creating the courses list (line by line). 
                   Break the read in course line into a String[] array.
                   We use the String#split() method for this with a small
                   Regular Expression (regex) to split each line based on
                   comma delimiters no matter how the delimiter spacing 
                   might be (ex: ","  " ,"  " , " or even "      ,   ").  */
                String[] coursesData = line.split("\s*,\s*");
                /* Add this above newly created coursesData string array 
                   to the list.   */
                cousesList.add(coursesData);
            }
            /* Write (append) this current record to new file. The String#format()
               method is used here to save the desired data into the 'StudentGPA.txt'
               file in a table style format.            */
            // Student Data...
            writer.append(String.format("%-12s: %-25s", "ID", studentData[1])).append(ls);
            writer.append(String.format("%-12s: %-25s", "Name", studentData[0])).append(ls);
            writer.append(String.format("%-12s: %-25s", "College", studentData[2])).append(ls);
            // Student Courses...
            // The Header line
            writer.append(String.format("%-13s %-30s %-10s %-4s", coursesHeader[0], 
                          coursesHeader[1], coursesHeader[2], coursesHeader[3])).append(ls);
            // Apply an Underline (underline1) under the header.
            writer.append(underline1).append(ls);
            // Write the Courses data in a table style format to make the Header format.
            for (String[] cData : cousesList) {
                writer.append(String.format("%-13s %-33s %-9s %-4s", 
                              cData[0], cData[1], cData[2], cData[3])).append(ls);
            }
            // Apply an Underline (underline2) under the Courses table.
            writer.append(underline2).append(ls);
                                    
            // Display In Console Window (you can delete this if you want)...
            System.out.println(String.format("%-12s: %-25s", "ID", studentData[1]));
            System.out.println(String.format("%-12s: %-25s", "Name", studentData[0]));
            System.out.println(String.format("%-12s: %-25s", "College", studentData[2]));
            System.out.println(String.format("%-13s %-30s %-10s %-4s", coursesHeader[0],
                               coursesHeader[1], coursesHeader[2], coursesHeader[3]));
            System.out.println(underline1);
            for (String[] cData : cousesList) {
                System.out.println(String.format("%-13s %-33s %-9s %-4s", 
                                    cData[0], cData[1], cData[2], cData[3]));
            }
            System.out.println(underline2);
                
            // The LAST line of each record, the Credits...
            // YOU DO THE CALCULATIONS FOR: totalAttemped, semGPA, and cumGPA
            String creditsAttempted = studentData[3];
            String creditsEarned = studentData[4];
            int credAttempted = 0;
            int credEarned = 0;
            int totalAttempted = 0;
            double semGPA = 0.0d;
            double cumGPA = 0.0d; 
            /* Make sure the 'credits attemted' numerical value is in fact 
               a string representaion of an integer value. if it is then
               convert that string numerical value to integer.  */
            if (creditsAttempted.matches("\d+")) {
                credAttempted = Integer.valueOf(creditsAttempted);
            }
            /* Make sure the 'credits earned' numerical value is in fact 
               a string representaion of an integer value. if it is then
               convert that string numerical value to integer.  */
            if (creditsEarned.matches("\d+")) {
                credEarned = Integer.valueOf(creditsEarned);
            }
            // Build the last record line (the Credits string) with the acquired data.
            String creditsString = new StringBuilder("CREDITS: TOTAL.ATTEMPTED ")
                    .append(totalAttempted).append("? EARNED ").append(credEarned)
                    .append(" ATTEMPTED ").append(credAttempted).append("  SEM GPA   ")
                    .append(semGPA).append("?   CUM GPA   ").append(cumGPA).append("?")
                    .toString();
            // Display it to the console Window (you can delete this).
            System.out.println(creditsString);
            System.out.println();
                
            // Write the built 'credit string' to file which finishes this record.
            writer.append(creditsString).append(ls);
            writer.append(ls);  // Blank Line in preparation for next record.
            writer.flush();     // Flush the data buffer - write record to disk NOW.
        }
    }
}
// Trap Errors...Do whatever you want with these.
catch (FileNotFoundException ex) {
    System.err.println("File Not Found!\n" + ex.getMessage());
}
catch (IOException ex) {
    System.err.println("IO Error Encountered!\n" + ex.getMessage());
}

是的,它看起来很长,但是如果您删除所有评论,您会发现它确实很长。不要害怕尝试代码。让它做你想做的事。


编辑:(根据评论)

将每条记录的学生信息部分放入 ArrayList 中,以便您可以按照自己的方式对其进行解析:

for循环位于上面用于收集学生信息的示例代码中,只需将此循环更改为以下代码并按照您想要的方式解析数据:

// Place this ArrayList declaration underneath the 'underline2' variable declaration:
java.util.ArrayList<String> studentInfo = new java.util.ArrayList<>();

然后:

if (line.startsWith("Student Name:")) {
    studentInfo.clear();
    studentInfo.add(line);
    /* Let's keep reading the file from this point on and retrieve 
       the other bits of student data to fill the studentData[] 
       Array...                      */
    for (int i = 1; i < 6; i++) {
        line = reader.nextLine().trim();
        /* If we encounter a comment line or a blank line then let's
           just skip past it. We don't want these.          */
        if (line.startsWith(";") || line.startsWith("#") || line.isEmpty()) {
            i--;
            continue;
        }
        studentInfo.add(line);
    }
    
    // .................................................
    // .... The rest of the code for this `if` block ...
    // .................................................
}

您当然需要在此循环后更改代码以正确表示此 ArrayList。