再談 .NET CSV 解析 - CsvHelper 與其他 38 種程式庫選擇
0 |
講到 .NET 解析 CSV,我習慣用 ServiceStack.Text CsvSerializer 處理,功能與效能都沒啥問題,也就一直沿用至今。今天在 FB 看到相關討論,發現有個我錯過的 CSV 程式庫 - CsvHelper (感謝網友許智涵分享),保哥之前寫過文章分享(參考:使用 C# 與 CsvHelper 套件解析《臺北市政府行政機關辦公日曆表》公開資料 ),甚至當年 FB 貼文讀者 Tomex Ou 也曾推薦,不過機緣未到沒進我的工具箱。程式開發就是這樣,找到順手的程式庫一直用不必多想是好事,風險是會錯過其他更方便快速的選擇,挑選工具多少也靠緣分,感覺是該重新評估了,於是有了這篇。
關於 .NET CSV 解析工具,我查到一篇能讓選擇障礙患者一秒發病的好文章 - The fastest CSV parser in .NET,作者 Joel Verhagen 是微軟 NuGet 開發人員,文章蒐集了 39 種 CSV 解析程式庫 (39 種!!! 這是要逼死誰?),進行一百萬筆 CSV 大檔解析效能測試。
在這個測試中,具備平行處理能力的 RecordParser 以 0.826s 奪冠,CsvHelper 以 2.484 秒排第七, ServiceStack.Text 則為 4.139s 排名 18,CsvHelper 效能明顯勝過 ServiceStack.Text。
要留意該測試不包含欄位雙引號、欄位內含逗號或換行,並有大量重複欄位值(對實作 String Pooling 的程式庫有利),同時也不考慮記憶體耗用,強型別映對能力等,故此測試結果挺極端,可能與一般實務應用需求有所出入。
但作者有聲明,如果你只是需要一款功能完整的 CSV 程式庫,不在意幾 ms 的速度差異,那麼西瓜偎大邊,選最受歡迎的 CsvHelper 就對了,被廣泛使用且歷經各種情境考驗仍能存活,品質與方便性必然不差,並有範例與參考資料豐富的優點。
NuGet 下載量 CsvHelper 190.9M vs ServiceStack.Text 46.8M。
CsvHelper,就決定是你了!
CsvHelper 說明文件及範例完整,不用擔心不會用,我打算將 ServiceStack.Text 範例改寫成 CsvHelper 版,檢測完自己最常用的情境就結束這回合。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Dynamic;
using CsvHelper;
using System.Globalization;
namespace CsvTest
{
class Program
{
public class Entity
{
public int Num { get; set; }
public DateTime Date { get; set; }
public string Text { get; set; }
public bool Flag { get; set; }
public Entity(int num, DateTime date, string text, bool flag)
{
Num = num;
Date = date;
Text = text;
Flag = flag;
}
public Entity() { } // CsvHelper需要無參數建構式
}
static Entity[] TestData = new Entity[]
{
new Entity(1, new DateTime(2012,12,21), "Normal", true),
new Entity(16, new DateTime(2012,12,21), "Taipei, Taiwan", true),
new Entity(32, new DateTime(2012,12,21), $@"""雙引號""跟,都來一下
換行當然不可少
", false)
};
static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Directory.CreateDirectory("output");
ObjArrayToCsv();
CsvToObjArray();
ParseAsStrArray();
}
//物件陣列轉成 CSV
static void ObjArrayToCsv()
{
using (var sw = new StreamWriter("output\\Test1.csv", false, Encoding.UTF8))
{
using (var csv = new CsvWriter(sw, CultureInfo.InvariantCulture))
{
csv.WriteRecords(TestData);
}
}
}
// CSV 還原成物件陣列
static void CsvToObjArray()
{
using var sr = new StreamReader("output\\Test1.csv", Encoding.UTF8);
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
var data = csv.GetRecords<Entity>().ToList();
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(data,
new System.Text.Json.JsonSerializerOptions {
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}));
Console.WriteLine();
}
// 不使用強型別,解析成 string 陣列
static void ParseAsStrArray()
{
string[] propNames = null;
List<string[]> rows = new List<string[]>();
using var sr = new StreamReader("output\\Test1.csv", Encoding.UTF8);
using var csv = new CsvReader(sr, CultureInfo.InvariantCulture);
while (csv.Read())
{
string[] strArray = Enumerable.Range(0, csv.Parser.Count)
.Select(i => csv.GetField(i)).ToArray();
if (propNames == null)
propNames = strArray;
else
rows.Add(strArray);
}
Console.WriteLine($"PropNames={string.Join(",", propNames)}");
for (int r = 0; r < rows.Count; r++)
{
var cells = rows[r];
for (int c = 0; c < cells.Length; c++)
{
Console.WriteLine($"[{r},{c}]={cells[c]}");
}
}
Console.WriteLine();
}
}
}
輕鬆秒殺~
My reason for changing CSV library from ServiceStack.Text to CsvHelper.
Comments
Be the first to post a comment