• .NET Core中的CSV解析库


    感谢

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

    .NET Core中的CSV解析库

    本篇博客来源于.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高很多,内存占用也少很多。

    最终结论

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

    附源代码

  • 相关阅读:
    CSS 层叠样式表
    一. 图论
    二. log4j配置文件
    三.注解
    3. Map与Tuple
    MappedByteBuffer读写文件
    2. scala中的数组
    1.scala语法
    二. 模式匹配
    一.算法的数学基础
  • 原文地址:https://www.cnblogs.com/lwqlun/p/9639456.html
Copyright © 2020-2023  润新知