1. 程式人生 > >.NET Core中的CSV解析庫

.NET Core中的CSV解析庫

ali 博客 背景 mapping 順序 不存在 obi mage 嵌套對象

感謝

本篇首先特別感謝從此啟程兄的《.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

附源代碼

技術分享圖片
作者:Lamond Lu
出處:https://www.cnblogs.com/lwqlun/p/9639456.html
本站使用「署名 4.0 國際」創作共享協議,轉載請在文章明顯位置註明作者及出處。

.NET Core中的CSV解析庫