一、任务分析
本次实战需完成按行读取成绩文件 → 数据处理分析 → 结果写入文件三大核心步骤,具体拆解:
- 组装对象数组:按行读取文本文件,将每行成绩数据封装为
Student对象,存入数组 / 列表; - 数据处理分析:计算个人总分 / 平均分、科目平均分、总分 / 单科前 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 | 写入文件,支持文本内容持久化 |
| 异常处理 | 捕获文件读取 / 写入、数据转换异常,保证程序健壮性 |
七、扩展优化(可选)
- 支持自定义成绩字段(如添加物理 / 化学);
- 按平均分排序,输出成绩等级(优秀 / 良好 / 及格 / 不及格);
- 读取 Excel 格式的成绩文件(需引入第三方库如 Apache POI);
- 增加数据去重逻辑,避免重复学生数据。