实战:LINQ与文件综合
学生成绩分析器
将学生数据序列化为JSON文件,再反序列化后用LINQ进行多维度统计分析:平均分、前三名、及格人数、按等级分组。
需求
- 定义
Student类,包含Name和Score属性 - 将学生列表序列化为JSON并保存到临时文件
- 从文件读取并反序列化
- 使用LINQ计算:平均分、前三名、及格(≥60)人数、按等级分组
示例
CSHARP
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main()
{
var students = new List<Student>
{
new Student { Name = "张三", Score = 92 },
new Student { Name = "李四", Score = 45 },
new Student { Name = "王五", Score = 78 },
new Student { Name = "赵六", Score = 88 },
new Student { Name = "钱七", Score = 55 },
new Student { Name = "孙八", Score = 95 },
new Student { Name = "周九", Score = 33 },
new Student { Name = "吴十", Score = 71 }
};
string tempDir = Path.GetTempPath();
string filePath = Path.Combine(tempDir, "students.json");
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(students, options);
File.WriteAllText(filePath, json);
Console.WriteLine("已写入: " + filePath);
string readJson = File.ReadAllText(filePath);
var loaded = JsonSerializer.Deserialize<List<Student>>(readJson);
double average = loaded.Average(s => s.Score);
Console.WriteLine($"平均分: {average:F1}");
var top3 = loaded.OrderByDescending(s => s.Score).Take(3);
Console.WriteLine("前三名:");
foreach (var s in top3)
{
Console.WriteLine($" {s.Name} - {s.Score}");
}
int passCount = loaded.Count(s => s.Score >= 60);
Console.WriteLine($"及格人数: {passCount}");
var grouped = loaded.GroupBy(s => s.Score >= 90 ? "优秀"
: s.Score >= 60 ? "及格" : "不及格");
Console.WriteLine("等级分组:");
foreach (var group in grouped)
{
Console.WriteLine($" {group.Key}: {string.Join(", ", group.Select(s => s.Name))}");
}
File.Delete(filePath);
}
}
TEXT
已写入: /tmp/students.json
平均分: 69.6
前三名:
孙八 - 95
张三 - 92
赵六 - 88
及格人数: 5
等级分组:
优秀: 张三, 孙八
不及格: 李四, 钱七, 周九
及格: 王五, 赵六, 吴十
简易日志分析器
读取文本日志文件,解析每一行,用LINQ统计错误/警告数量、查找最高频错误、按日期范围过滤。
需求
- 日志格式:
[日期] [级别] 消息,如[2025-01-15] [ERROR] 磁盘已满 - 统计各级别数量
- 找出出现次数最多的ERROR消息
- 按日期范围过滤日志
示例
CSHARP
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
string tempDir = Path.GetTempPath();
string logPath = Path.Combine(tempDir, "app.log");
var lines = new List<string>
{
"[2025-01-10] [INFO] 系统启动",
"[2025-01-10] [WARN] 内存使用率80%",
"[2025-01-11] [ERROR] 数据库连接失败",
"[2025-01-11] [ERROR] 磁盘已满",
"[2025-01-12] [INFO] 用户登录",
"[2025-01-12] [ERROR] 数据库连接失败",
"[2025-01-13] [WARN] CPU使用率90%",
"[2025-01-13] [ERROR] 网络超时",
"[2025-01-14] [INFO] 定时任务完成",
"[2025-01-14] [ERROR] 数据库连接失败"
};
File.WriteAllLines(logPath, lines);
var logEntries = File.ReadAllLines(logPath)
.Select(line =>
{
var parts = line.Split(']');
return new
{
Date = parts[0].TrimStart('[').Trim(),
Level = parts[1].TrimStart('[').Trim(),
Message = parts[2].Trim()
};
})
.ToList();
var levelCounts = logEntries
.GroupBy(e => e.Level)
.Select(g => new { Level = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine("级别统计:");
foreach (var item in levelCounts)
{
Console.WriteLine($" {item.Level}: {item.Count}条");
}
var topError = logEntries
.Where(e => e.Level == "ERROR")
.GroupBy(e => e.Message)
.OrderByDescending(g => g.Count())
.First();
Console.WriteLine($"最高频错误: {topError.Key} ({topError.Count()}次)");
var filtered = logEntries
.Where(e => string.Compare(e.Date, "2025-01-11") >= 0
&& string.Compare(e.Date, "2025-01-13") <= 0)
.ToList();
Console.WriteLine("1月11日-13日日志:");
foreach (var entry in filtered)
{
Console.WriteLine($" [{entry.Date}] [{entry.Level}] {entry.Message}");
}
File.Delete(logPath);
}
}
TEXT
级别统计:
ERROR: 5条
INFO: 3条
WARN: 2条
最高频错误: 数据库连接失败 (3次)
1月11日-13日日志:
[2025-01-11] [ERROR] 数据库连接失败
[2025-01-11] [ERROR] 磁盘已满
[2025-01-12] [INFO] 用户登录
[2025-01-12] [ERROR] 数据库连接失败
[2025-01-13] [WARN] CPU使用率90%
[2025-01-13] [ERROR] 网络超时
文件批量重命名工具
列出目录中的文件,用LINQ按扩展名过滤,再批量添加前缀或后缀。
需求
- 在临时目录创建示例文件
- 按扩展名筛选文件
- 为文件名添加前缀和后缀(保留扩展名)
- 展示重命名逻辑(输出新旧名称对比)
示例
CSHARP
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
string workDir = Path.Combine(Path.GetTempPath(), "rename_demo");
Directory.CreateDirectory(workDir);
var demoFiles = new[] { "photo1.jpg", "photo2.jpg", "doc.txt", "notes.txt", "data.csv" };
foreach (var f in demoFiles)
{
File.WriteAllText(Path.Combine(workDir, f), "demo");
}
Console.WriteLine("原始文件:");
foreach (var f in Directory.GetFiles(workDir))
{
Console.WriteLine(" " + Path.GetFileName(f));
}
string targetExt = ".txt";
string prefix = "backup_";
var filesToRename = Directory.GetFiles(workDir)
.Where(f => Path.GetExtension(f).Equals(targetExt, StringComparison.OrdinalIgnoreCase))
.ToList();
Console.WriteLine($"\n筛选扩展名 {targetExt}:");
foreach (var f in filesToRename)
{
string dir = Path.GetDirectoryName(f);
string nameNoExt = Path.GetFileNameWithoutExtension(f);
string newName = prefix + nameNoExt + targetExt;
string newPath = Path.Combine(dir, newName);
Console.WriteLine($" {Path.GetFileName(f)} -> {newName}");
File.Move(f, newPath);
}
Console.WriteLine("\n重命名后:");
foreach (var f in Directory.GetFiles(workDir))
{
Console.WriteLine(" " + Path.GetFileName(f));
}
Directory.Delete(workDir, true);
}
}
TEXT
原始文件:
photo1.jpg
photo2.jpg
doc.txt
notes.txt
data.csv
筛选扩展名 .txt:
doc.txt -> backup_doc.txt
notes.txt -> backup_notes.txt
重命名后:
photo1.jpg
photo2.jpg
backup_doc.txt
backup_notes.txt
data.csv
❓ 常见问题
Q JSON反序列化时属性名不匹配怎么办?
A 使用
[JsonPropertyName] 特性标注映射名称,或自定义 JsonSerializerOptions 的 PropertyNamingPolicy。Q LINQ的GroupBy分组顺序是固定的吗?
A 不是,GroupBy不保证分组顺序,需用OrderBy排序后再遍历。
Q File.Move重命名时目标文件已存在会怎样?
A 会抛出IOException,应先检查目标路径是否存在或使用File.Replace。
Q 读取大日志文件用ReadAllLines合适吗?
A 不合适,ReadAllLines会一次性加载全部内容到内存,大文件应使用File.ReadLines进行懒加载逐行读取。
📖 小节
- 综合运用LINQ与JSON序列化完成数据持久化与统计分析
- 掌握日志解析、LINQ分组统计与日期过滤技巧
- 使用Directory和Path类结合LINQ实现文件筛选与批量操作
- 所有文件操作示例使用Path.GetTempPath()确保跨平台兼容
- 临时文件和目录在使用后及时清理,避免残留
📝 作业
- 扩展学生成绩分析器,增加按分数段(0-59/60-79/80-89/90-100)统计人数并输出柱状图(用
*表示) - 为日志分析器添加功能:统计每天的ERROR数量,输出连续出现ERROR最多的日期区间
- 改造文件重命名工具,支持通过命令行参数指定目录路径、扩展名过滤条件和前缀,并添加
--dry-run模式只显示预览不实际执行



