.NET Core中的CSV解析库

感谢

本篇首先特别感谢今后启程兄的《.NetCore外国一些高质量博客分享》, 发现不少国外的.NET Core技术博客资源, 我会不按期从中选择一些有意思的文章翻译总结一下。html

.NET Core中的CSV解析库

本篇博客来源于.NET Core Totorials的《CSV Parsing In .NET Core》git

背景介绍

对于初级程序员来讲, 使用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。github

测试例子

为了测试这些CSV解析库,咱们首先建立一个.NET Core的控制台程序app

而后咱们添加一个Automobile类,其代码以下ide

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类的对象实例中。测试

其内容以下ui

Make,Model,Type,Year,Price,Comment
"Toyota",Corolla,Car,1990,2000.99,"Comment with a,
line break and "" quotes"

这个文件中第一行是一个表头,第二行是一个数据行,数据行中包含了this

  • 字符串内容换行
  • 字符串中有逗号
  • 字符串中有双引号

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高不少,内存占用也少不少。

最终结论

  • 当不须要支持字符串换行的时候,请使用Tiny Csv Parser
  • 当须要支持字符串换行的时候,请使用CSVHelper

附源代码

相关文章
相关标签/搜索