感谢
本篇首先特别感谢从此启程兄的《.NetCore 外国一些高质量博客分享》, 发现很多国外的. NET Core 技术博客资源, 我会不定期从中选择一些有意思的文章翻译总结一下.
本篇博客来源于. NET Core Totorials 的《CSV Parsing In .NET Core》.
背景介绍
对于初级程序员来说, 使用 string.Split(',') 来解析 CSV 文件基本就是唯一可行的方法, 但是之后他们会发现除了使用逗号分隔值之外, CSV 中还有其他需要处理的东西, 所以作者就介绍了 CSV 解析的一些痛点并推荐了 2 个比较好用 CSV 解析库.
CSV 解析一些痛点
一个 CSV 文件有可能有表头, 也可能没有表头. 如果表头存在的话, 解析 CSV 时, 列的顺序就不太重要了, 因为你可以根据表头知道所需的数据在第几列. 如果表头不存在的话, 解析 CSV 时, 就需要依赖列的顺序. 所以 CSV 的解析, 应该即支持表头, 也支持按列的顺序.
CSV 文件中某一列的值可能是带双引号的字符串, 字符串中可能包含换行符, 逗号, 双引号.
例 1:1,2,"a,b"
例 2: 1,2,"a[换行符]b"
例 3: 1,2,"this is""Good""." (注: 双引号字符串中的出现的连续双引号表示转义, 这里真正的文本是 this is "Good".)
CSV 文件中每一行的数据的数据列数量 "应该" 一样, 但不是必须一样, 所以解析 CSV 需要处理这些不一致的情况
在. NET 中, 当反序列化一个 CSV 文件的时候, 还需要
支持反序列化成集合
支持枚举
支持自定义映射
支持映射嵌套对象
.NET Core 中的一些优秀 CSV 解析库
这里作者推荐了 2 个 CSV 解析库, 一个是 CSVHelper, 一个是 Tiny CSV Parser.
测试例子
为了测试这些 CSV 解析库, 我们首先创建一个. NET Core 的控制台程序
然后我们添加一个 Automobile 类, 其代码如下
- public class Automobile
- {
- public string Make { get; set; }
- public string Model { get; set; }
- public AutomobileType Type { get; set; }
- public int Year { get; set; }
- public decimal Price { get; set; }
- public AutomobileComment Comment { get; set; }
- public override string ToString()
- {
- StringBuilder builder = new StringBuilder();
- builder.AppendLine();
- builder.AppendLine($"Make: {Make}");
- builder.AppendLine($"Model: {Model}");
- builder.AppendLine($"Type: {Type.ToString()}");
- builder.AppendLine($"Year: {Year}");
- builder.AppendLine($"Price: {Price}");
- builder.AppendLine($"Comment: {Comment?.Comment}");
- return builder.ToString();
- }
- }
- public class AutomobileComment
- {
- public string Comment { get; set; }
- }
- public enum AutomobileType
- {
- None,
- Car,
- Truck,
- Motorbike
- }
最后我们创建一个 csv 文件 sample.txt 作为测试文件, 我们希望将当前 csv 文件中的数据, 反序列化到一个 Automobile 类的对象实例中.
其内容如下
- Make,Model,Type,Year,Price,Comment
- "Toyota",Corolla,Car,1990,2000.99,"Comment with a,
- line break and ""quotes"
这个文件中第一行是一个表头, 第二行是一个数据行, 数据行中包含了
字符串内容换行
字符串中有逗号
字符串中有双引号
CSVHelper
CSVHelper 是一个 CSV 文件的读写库. 它支持读写自定义类对象. 官网地址 https://joshclose.github.io/CsvHelper/
安装
我们可以使用 Package Manager Console 来安装 CSVHelper.
命令如下:
PM> Install-Package CsvHelper
解析 CSV
使用 CSVHelper 解析 CSV 文件代码很简单, 还需要 2 步
使用 CsvReader 类的对象实例读取 CSV 文件
使用 GetRecords 方法来反序列化
- using (TextReader reader = new StreamReader("sample.txt"))
- {
- var csvReader = new CsvReader(reader);
- var records = csvReader.GetRecords<Automobile>();
- foreach (var r in records)
- {
- Console.WriteLine(r.ToString());
- }
- }
最终结果
从结果上看, 上面提到的 CSV 解析痛点, CSVHelper 都实现了, 特别是针对 Comment 字段中的逗号, 换行, 双引号, CSVHelper 都处理的很成功.
Tiny CSV Parser
下一个介绍的 CSV 解析器是 Ting CSV Parser, 官网 http://bytefish.github.io/TinyCsvParser/index.html, 它是使用配置的方式映射 CSV 字段, 使用方式上有点类似于 AutoMapper
安装
我们可以使用 Package Manager Console 来安装 Tiny CSV Parser.
命令如下:
PM> Install-Package TinyCsvParser
解析 CSV
使用 Tiny CSV Parser 解析 CSV 文件, 首先我们需要创建一个映射类. 映射类需要继承自 CsvMapping
映射类代码
- public class CsvAutomobileMapping : CsvMapping<Automobile>
- {
- public CsvAutomobileMapping() : base()
- {
- MapProperty(0, x => x.Make);
- MapProperty(1, x => x.Model);
- MapProperty(2, x => x.Type, new EnumConverter<AutomobileType>());
- MapProperty(3, x => x.Year);
- MapProperty(4, x => x.Price);
- MapProperty(5, x => x.Comment, new AutomobileCommentTypeConverter());
- }
- }
- public class AutomobileCommentTypeConverter : ITypeConverter<AutomobileComment>
- {
- public Type TargetType => typeof(AutomobileComment);
- public bool TryConvert(string value, out AutomobileComment result)
- {
- result = new AutomobileComment
- {
- Comment = value
- };
- return true;
- }
- }
其中有几个要点,
MapProperty 是根据列的索引来映射属性的.
当映射枚举时, 需要使用 EnumConverter 来映射.
当映射子对象的时候, 需要创建子对象对应的 Converter, 例如
- AutomobileCommentTypeConverter
- .
然后我们修改 Program.cs, 使用 CsvParser 来解析 sample.txt
- CsvParserOptions csvParserOptions = new CsvParserOptions(true, ',');
- var csvParser = new CsvParser<Automobile>(csvParserOptions, new CsvAutomobileMapping());
- var records = csvParser.ReadFromFile("sample.txt", Encoding.UTF8);
- foreach (var r in records)
- {
- if (r.IsValid)
- {
- Console.WriteLine(r.Result.ToString());
- }
- }
最终结果
从结果上看, Tiny CSV Parser 实现了大部分 CSV 解析的痛点, 唯一不支持的是字符串换行, 这一点需要注意.
效率比较
文章的最后, 作者使用 Benchmark 对 CSVHelper 和 Tiny CSV Parser 进行了效率比较.
测试代码如下:
- [MemoryDiagnoser]
- public class CsvBenchmarking
- {
- [Benchmark(Baseline =true)]
- public IEnumerable<Automobile> CSVHelper()
- {
- TextReader reader = new StreamReader("import.txt");
- var csvReader = new CsvReader(reader);
- var records = csvReader.GetRecords<Automobile>();
- return records.ToList();
- }
- [Benchmark]
- public IEnumerable<Automobile> TinyCsvParser()
- {
- CsvParserOptions csvParserOptions = new CsvParserOptions(true, ',');
- var csvParser = new CsvParser<Automobile>(csvParserOptions, new CsvAutomobileMapping());
- var records = csvParser.ReadFromFile("import.txt", Encoding.UTF8);
- return records.Select(x => x.Result).ToList();
- }
- }
当测试 100000 行数据的时候
当测试 1000000 行数据的时候
从测试结果上看
Tiny Csv Parser 的效率比 CSVHelper 高很多, 内存占用也少很多.
来源: https://www.cnblogs.com/lwqlun/p/9639456.html