# 文件读写-成绩

37 阅读5分钟

一、任务分析

本次实战需完成按行读取成绩文件 → 数据处理分析 → 结果写入文件三大核心步骤,具体拆解:

  1. 组装对象数组:按行读取文本文件,将每行成绩数据封装为Student对象,存入数组 / 列表;
  2. 数据处理分析:计算个人总分 / 平均分、科目平均分、总分 / 单科前 3 名;
  3. 保存结果到文件:将所有分析结果写入新文件,完成数据持久化。

二、按行读取文本文件

核心思路

由于每行文本对应一条学生成绩数据(格式示例:张三,90,85,95),需按行读取文件,保证数据完整性。

代码演示(读取并打印每行数据)

import scala.io.Source

/**
 * 按行读取文件示例
 * 功能:读取input.txt,逐行打印文件内容
 */
object SourceReadExample {
  def main(args: Array[String]): Unit = {
    // 1. 创建文件数据源对象
    val source = Source.fromFile("input.txt")
    
    try {
      // 2. getLines():按行读取文件,返回迭代器
      for (line <- source.getLines()) {
        println(s"读取到数据行:$line")
      }
    } catch {
      // 捕获文件不存在/读取异常
      case ex: Exception => println(s"文件读取失败:${ex.getMessage}")
    } finally {
      // 3. 关闭数据源,释放资源
      source.close()
    }
  }
}

关键知识点讲解

  • getLines():Scala Source 类的核心方法,返回文件行的迭代器,逐行读取避免一次性加载大文件;
  • 必须调用source.close():释放文件句柄,避免资源泄漏;
  • 按行读取的必要性:匹配 “一行一条成绩数据” 的文件格式,便于后续拆分字段。

三、组装数据对象

核心思路

定义case class(样例类)封装学生成绩信息,遍历读取的每行数据,拆分字段后创建Student对象,存入列表。

完整代码(组装 Student 对象列表)

import scala.io.Source
import scala.collection.mutable.ListBuffer

// 定义样例类,封装学生成绩(name:姓名,yuwen/shuuxue/yingyu:各科成绩)
case class Student(name: String, yuwen: Double, shuxue: Double, yingyu: Double)

/**
 * 组装学生成绩对象列表
 */
object StudentDataAssemble {
  def main(args: Array[String]): Unit = {
    // 初始化可变列表,存储Student对象
    val stuList = new ListBuffer[Student]()
    val source = Source.fromFile("input.txt")

    try {
      // 遍历每行数据,拆分并创建对象
      for (line <- source.getLines()) {
        // 跳过空行
        if (line.nonEmpty) {
          // 按逗号拆分字段(需保证文件格式为:姓名,语文,数学,英语)
          val fields = line.split(",")
          // 校验字段数量,避免格式错误导致异常
          if (fields.length == 4) {
            val name = fields(0).trim
            // 字符串转Double,处理非数字异常
            val yuwen = try { fields(1).trim.toDouble } catch { case _: Exception => 0.0 }
            val shuxue = try { fields(2).trim.toDouble } catch { case _: Exception => 0.0 }
            val yingyu = try { fields(3).trim.toDouble } catch { case _: Exception => 0.0 }
            // 添加到列表
            stuList += Student(name, yuwen, shuxue, yingyu)
          } else {
            println(s"数据格式错误,跳过此行:$line")
          }
        }
      }

      // 打印验证结果
      println("\n✅ 组装的学生对象列表:")
      stuList.foreach(stu => println(s"姓名:${stu.name},语文:${stu.yuwen},数学:${stu.shuxue},英语:${stu.yingyu}"))
    } catch {
      case ex: Exception => println(s"组装数据失败:${ex.getMessage}")
    } finally {
      source.close()
    }
  }
}

关键知识点讲解

  • case class:自动生成构造器、toString、equals 等方法,简化数据封装;
  • ListBuffer:可变列表,适合动态添加元素(Scala 默认 List 不可变);
  • 数据校验:拆分字段后校验长度、转换数值时捕获异常,避免文件格式错误导致程序崩溃。

四、分析数据(核心业务逻辑)

前置准备:整合读取 + 组装逻辑

import scala.io.Source
import scala.collection.mutable.ListBuffer

case class Student(name: String, yuwen: Double, shuxue: Double, yingyu: Double)

object StudentScoreAnalysis {
  def main(args: Array[String]): Unit = {
    // 1. 读取并组装学生列表
    val stuList = loadStudentData("input.txt")
    if (stuList.isEmpty) {
      println("❌ 无有效学生数据,程序退出")
      return
    }

    // 2. 执行所有分析任务
    // 任务1:计算个人总分和平均分
    val studentScoreDetail = calculatePersonalScore(stuList)
    // 任务2:统计各科平均分
    val subjectAvgScore = calculateSubjectAvg(stuList)
    // 任务3:获取总分/单科前3名
    val top3Total = getTop3ByTotal(studentScoreDetail)
    val top3Yuwen = getTop3BySubject(stuList, "yuwen")
    val top3Shuxue = getTop3BySubject(stuList, "shuxue")
    val top3Yingyu = getTop3BySubject(stuList, "yingyu")

    // 3. 打印所有分析结果
    printAnalysisResult(studentScoreDetail, subjectAvgScore, top3Total, top3Yuwen, top3Shuxue, top3Yingyu)

    // 4. 保存结果到文件
    saveResultToFile("score_analysis_result.txt", studentScoreDetail, subjectAvgScore, top3Total, top3Yuwen, top3Shuxue, top3Yingyu)
  }

  // 封装:读取文件并组装学生列表
  def loadStudentData(filePath: String): ListBuffer[Student] = {
    val stuList = new ListBuffer[Student]()
    val source = Source.fromFile(filePath)
    try {
      for (line <- source.getLines() if line.nonEmpty) {
        val fields = line.split(",")
        if (fields.length == 4) {
          val name = fields(0).trim
          val yuwen = try { fields(1).trim.toDouble } catch { case _: Exception => 0.0 }
          val shuxue = try { fields(2).trim.toDouble } catch { case _: Exception => 0.0 }
          val yingyu = try { fields(3).trim.toDouble } catch { case _: Exception => 0.0 }
          stuList += Student(name, yuwen, shuxue, yingyu)
        }
      }
    } catch {
      case ex: Exception => println(s"读取数据失败:${ex.getMessage}")
    } finally {
      source.close()
    }
    stuList
  }

任务 1:计算每个人的总分和平均分、

  // 封装:计算个人总分、平均分
  def calculatePersonalScore(stuList: ListBuffer[Student]): ListBuffer[(String, Double, Double, Double, Double, Double)] = {
    val detailList = new ListBuffer[(String, Double, Double, Double, Double, Double)]()
    stuList.foreach { stu =>
      val total = stu.yuwen + stu.shuxue + stu.yingyu // 总分
      val avg = total / 3 // 平均分(保留2位小数)
      detailList += ((stu.name, stu.yuwen, stu.shuxue, stu.yingyu, total, BigDecimal(avg).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble))
    }
    detailList
  }

任务 2:统计每个科目的平均分

// 封装:统计各科平均分
  def calculateSubjectAvg(stuList: ListBuffer[Student]): (Double, Double, Double) = {
    val totalStu = stuList.size
    if (totalStu == 0) return (0.0, 0.0, 0.0)

    // 计算各科总分
    var yuwenTotal = 0.0
    var shuxueTotal = 0.0
    var yingyuTotal = 0.0

    stuList.foreach { stu =>
      yuwenTotal += stu.yuwen
      shuxueTotal += stu.shuxue
      yingyuTotal += stu.yingyu
    }

    // 计算平均分(保留2位小数)
    val yuwenAvg = BigDecimal(yuwenTotal / totalStu).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble
    val shuxueAvg = BigDecimal(shuxueTotal / totalStu).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble
    val yingyuAvg = BigDecimal(yingyuTotal / totalStu).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

    (yuwenAvg, shuxueAvg, yingyuAvg)
  }

任务 3:列出总分前 3 名和单科前 3 名

  // 封装:获取总分前3名
  def getTop3ByTotal(detailList: ListBuffer[(String, Double, Double, Double, Double, Double)]): ListBuffer[(String, Double)] = {
    // 按总分降序排序,取前3
    detailList.sortBy(_._5).reverse.take(3).map(item => (item._1, item._5))
  }

  // 封装:获取单科前3名(subject:yuwen/shuxue/yingyu)
  def getTop3BySubject(stuList: ListBuffer[Student], subject: String): ListBuffer[(String, Double)] = {
    val sortedList = subject match {
      case "yuwen" => stuList.sortBy(_.yuwen).reverse
      case "shuxue" => stuList.sortBy(_.shuxue).reverse
      case "yingyu" => stuList.sortBy(_.yingyu).reverse
      case _ => stuList
    }
    // 取前3并封装(姓名+单科成绩)
    sortedList.take(3).map(stu => {
      val score = subject match {
        case "yuwen" => stu.yuwen
        case "shuxue" => stu.shuxue
        case "yingyu" => stu.yingyu
        case _ => 0.0
      }
      (stu.name, score)
    })
  }

辅助:打印分析结果到控制台

// 封装:打印所有分析结果
  def printAnalysisResult(
                           studentDetail: ListBuffer[(String, Double, Double, Double, Double, Double)],
                           subjectAvg: (Double, Double, Double),
                           top3Total: ListBuffer[(String, Double)],
                           top3Yuwen: ListBuffer[(String, Double)],
                           top3Shuxue: ListBuffer[(String, Double)],
                           top3Yingyu: ListBuffer[(String, Double)]
                         ): Unit = {
    println("=" * 60)
    println("📊 学生成绩分析结果")
    println("=" * 60)

    // 1. 个人总分&平均分
    println("\n1. 个人成绩详情(姓名 | 语文 | 数学 | 英语 | 总分 | 平均分):")
    studentDetail.foreach { case (name, yw, sx, yy, total, avg) =>
      println(f"$name%-6s | $yw%5.1f | $sx%5.1f | $yy%5.1f | $total%5.1f | $avg%5.2f")
    }

    // 2. 各科平均分
    println("\n2. 各科平均分:")
    println(f"语文:${subjectAvg._1}%.2f | 数学:${subjectAvg._2}%.2f | 英语:${subjectAvg._3}%.2f")

    // 3. 总分前3名
    println("\n3. 总分前3名:")
    top3Total.zipWithIndex.foreach { case ((name, total), idx) =>
      println(f"第${idx + 1}名:$name,总分:$total%.1f")
    }

    // 4. 单科前3名
    println("\n4. 语文前3名:")
    top3Yuwen.zipWithIndex.foreach { case ((name, score), idx) =>
      println(f"第${idx + 1}名:$name,成绩:$score%.1f")
    }
    println("\n5. 数学前3名:")
    top3Shuxue.zipWithIndex.foreach { case ((name, score), idx) =>
      println(f"第${idx + 1}名:$name,成绩:$score%.1f")
    }
    println("\n6. 英语前3名:")
    top3Yingyu.zipWithIndex.foreach { case ((name, score), idx) =>
      println(f"第${idx + 1}名:$name,成绩:$score%.1f")
    }
  }

五、保存结果到文件

  // 封装:保存分析结果到文件
  def saveResultToFile(
                        filePath: String,
                        studentDetail: ListBuffer[(String, Double, Double, Double, Double, Double)],
                        subjectAvg: (Double, Double, Double),
                        top3Total: ListBuffer[(String, Double)],
                        top3Yuwen: ListBuffer[(String, Double)],
                        top3Shuxue: ListBuffer[(String, Double)],
                        top3Yingyu: ListBuffer[(String, Double)]
                      ): Unit = {
    import java.io.PrintWriter
    val writer = new PrintWriter(filePath)

    try {
      // 写入文件头
      writer.write("=" * 60 + "\n")
      writer.write("学生成绩分析报告\n")
      writer.write("=" * 60 + "\n\n")

      // 1. 个人成绩详情
      writer.write("1. 个人成绩详情(姓名 | 语文 | 数学 | 英语 | 总分 | 平均分):\n")
      studentDetail.foreach { case (name, yw, sx, yy, total, avg) =>
        writer.write(f"$name%-6s | $yw%5.1f | $sx%5.1f | $yy%5.1f | $total%5.1f | $avg%5.2f\n")
      }

      // 2. 各科平均分
      writer.write("\n2. 各科平均分:\n")
      writer.write(f"语文:${subjectAvg._1}%.2f | 数学:${subjectAvg._2}%.2f | 英语:${subjectAvg._3}%.2f\n")

      // 3. 总分前3名
      writer.write("\n3. 总分前3名:\n")
      top3Total.zipWithIndex.foreach { case ((name, total), idx) =>
        writer.write(f"第${idx + 1}名:$name,总分:$total%.1f\n")
      }

      // 4. 单科前3名
      writer.write("\n4. 语文前3名:\n")
      top3Yuwen.zipWithIndex.foreach { case ((name, score), idx) =>
        writer.write(f"第${idx + 1}名:$name,成绩:$score%.1f\n")
      }
      writer.write("\n5. 数学前3名:\n")
      top3Shuxue.zipWithIndex.foreach { case ((name, score), idx) =>
        writer.write(f"第${idx + 1}名:$name,成绩:$score%.1f\n")
      }
      writer.write("\n6. 英语前3名:\n")
      top3Yingyu.zipWithIndex.foreach { case ((name, score), idx) =>
        writer.write(f"第${idx + 1}名:$name,成绩:$score%.1f\n")
      }

      println(s"\n✅ 分析结果已保存到文件:$filePath")
    } catch {
      case ex: Exception => println(s"❌ 写入文件失败:${ex.getMessage}")
    } finally {
      writer.close()
    }
  }
}

六、使用说明(可直接复制运行)

1. 准备输入文件

在项目根目录创建input.txt,按以下格式写入学生成绩(每行一条):

张三,90,85,95
李四,88,92,80
王五,78,95,88
赵六,95,80,92
钱七,85,88,90

2. 运行代码

  • 复制上述所有代码到在线 Scala 运行环境(如 ScalaFiddle、JDoodle)或本地 Scala IDE;
  • 执行StudentScoreAnalysis对象的main方法;
  • 控制台会打印分析结果,同时生成score_analysis_result.txt文件保存结果。

3. 核心知识点总结

知识点作用
Source.getLines()按行读取文件,返回迭代器
case class简化数据封装,自动生成常用方法
ListBuffer可变列表,适合动态添加元素
sortBy + reverse降序排序(sortBy默认升序,reverse翻转为降序)
BigDecimal精确处理小数,避免浮点数精度问题(如平均分保留 2 位小数)
PrintWriter写入文件,支持文本内容持久化
异常处理捕获文件读取 / 写入、数据转换异常,保证程序健壮性

七、扩展优化(可选)

  1. 支持自定义成绩字段(如添加物理 / 化学);
  2. 按平均分排序,输出成绩等级(优秀 / 良好 / 及格 / 不及格);
  3. 读取 Excel 格式的成绩文件(需引入第三方库如 Apache POI);
  4. 增加数据去重逻辑,避免重复学生数据。