前言
嗨,各位程序员小伙伴们,早上好!
在你的编程旅程中,有没有遇到过这样的“噩梦”场景?
“老板让我处理一个 8GB 的用户数据 CSV 文件,我一加载,电脑直接卡死!”
“这个报表要读取上百万条记录,还没处理完就 OutOfMemory 了……”
……
很熟悉的场景,是不是?
这些问题的本质是:传统方式试图把整个文件“一口吞下”,而我们的内存根本不够用。
你可能不知道,C# 为我们提供了一个优雅的解决方案——yield return。
它就像一个“懒加载”的魔法,让你可以按需生成数据,而不是一次性全部创建。
用它来处理 CSV 文件,你可以做到:
- 无论文件多大,内存只保存当前正在处理的一行,内存占用极低。
- 无需等待整个文件加载完成,解析完第一行就能立刻开始处理,启动速度快
- 轻松与 LINQ 结合,实现 Take()、Where()、Select() 等操作,可组合性强
今天,我们就来手把手教你用 yield 封装一个轻量级的 CSV 解析器,让你在 5 分钟内搞定 10GB 的海量数据!
准备好了吗?Let's go!
封装类
下面是封装的 CSV 解析类,核心就是 yield return 的使用,留意代码中的注释
/// <summary>
/// CSV 文件解析器
/// 使用 yield 实现惰性求值,支持流式处理海量数据
/// </summary>
publicclassCsvParser
{
/// <summary>
/// 解析 CSV 文件,返回一个可枚举的序列
/// </summary>
/// <typeparam name="T">目标对象类型</typeparam>
/// <param name="filePath">CSV 文件路径</param>
/// <param name="mapper">将字符串数组映射为 T 类型对象的函数</param>
/// <returns>可枚举的 T 类型对象序列</returns>
public IEnumerable<T> ParseCsvFile<T>(string filePath, Func<string[], T> mapper)
{
// 使用 StreamReader 逐行读取文件,确保资源正确释放
usingvar reader = new StreamReader(filePath);
// 读取第一行作为标题(通常 CSV 第一行是列名)
string header = reader.ReadLine();
Console.WriteLine($"CSV标题: {header}");
string line;
// 循环读取每一行,直到文件末尾
while ((line = reader.ReadLine()) != null)
{
// 按逗号分割每一行,得到字段数组
var fields = line.Split(',');
// 简单清理字段:去除引号和首尾空格
// 这里可根据实际业务需要进行处理
for (int i = 0; i < fields.Length; i++)
{
fields[i] = fields[i].Trim('\"').Trim();
}
// 使用传入的映射函数,将字符串数组转换为业务对象
// yield return 是关键:它不会立即返回所有数据,
// 而是在 foreach 遍历时“按需”生成下一条数据
yield return mapper(fields);
}
// 注意:using 语句确保 reader 在枚举结束或异常时自动关闭
}
}
使用示例
假设有一个 User 类,用于存储用户信息:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreateTime { get; set; }
}
现在,我们来使用 CsvParser 处理一个巨大的 CSV 文件:
// 创建解析器实例
var parser = new CsvParser();
// 解析 CSV 文件,返回一个 IEnumerable<User>
// 注意:此时文件并未完全读取,只是建立了“读取计划”
var users = parser.ParseCsvFile(@"D:\data\users.csv", fields => new User
{
Id = int.Parse(fields[0]),
Name = fields[1],
Email = fields[2],
CreateTime = DateTime.Parse(fields[3])
});
// 开始遍历!数据将逐行读取和处理
// 例如:只处理前 10 条数据(避免一次性输出太多)
foreach (var user in users.Take(10))
{
Console.WriteLine($"用户: {user.Name}, 邮箱: {user.Email}");
}
// 可以根据实际业务需求做更多操作,比如:
// var activeUsers = users.Where(u => u.CreateTime > DateTime.Now.AddYears(-1));
// var totalUsers = users.Count(); // 注意:这会遍历整个文件
总结
我们用一个简单的 yield return,就实现了一个高效、低内存的 CSV 解析器,内存不再是瓶颈,无论文件是 10MB 还是 10GB,内存占用都几乎不变。
如果你有相类似的业务需求,不妨在你的项目试一试,相信不会让你失望的!
阅读原文:原文链接
该文章在 2025/10/29 18:58:00 编辑过