.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

附源代碼

相關文章
相關標籤/搜索