.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

附源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM