|
|
|
|
|
C# .NET 解析并讀寫 CSV 文件,本文詳細(xì)介紹如何使用 CsvHelper 來完成這個(gè)操作。
CsvHelper 介紹
CsvHelper 是一個(gè)用于讀取和寫入CSV文件的.NET庫(kù),非常快速、靈活且易于使用。
CsvHelper 有如下幾大特征:
GetRecords<T>()
和 WriteRecords(records)
一樣簡(jiǎn)單。無(wú)需配置。CsvHelper 官網(wǎng):
https://joshclose.github.io/CsvHelper/
開始安裝使用
訪問下面官方地址下載安裝包。
https://www.nuget.org/packages/CsvHelper/
包管理器控制臺(tái)
PM> Install-Package CsvHelper
.NET CLI 控制臺(tái)
> dotnet add package CsvHelper
CsvHelper 要求你指定要使用的 CultureInfo
,用于確定類型轉(zhuǎn)換時(shí)的默認(rèn)分隔符、默認(rèn)行結(jié)束符和格式。你也可以更改其中任何一個(gè)配置,為你的數(shù)據(jù)選擇合適的區(qū)域性。InvariantCulture
在讀寫文件時(shí)可移植性最強(qiáng),因此將在大多數(shù)示例中使用。
默認(rèn)情況下,CsvHelper 將遵循RFC 4180并用\r\n
編寫換行符,無(wú)論你在什么操作系統(tǒng)上運(yùn)行。CsvHelper 可以讀取\r\n
、\r
或\n
不進(jìn)行任何配置更改。如果要以非標(biāo)準(zhǔn)格式讀取或?qū)懭?,可以更?code>NewLine。
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
NewLine = Environment.NewLine,
};
讀 CSV 文件
假設(shè)我們有如下所示的 CSV 文件。
Id,Name
1,one
2,two
和一個(gè)看起來像這樣的類定義。
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
如果我們的類屬性名稱與我們的 CSV 文件頭名稱相匹配,我們可以在不進(jìn)行任何配置的情況下讀取該文件。
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Foo>();
}
該GetRecords<T>
方法將返回一個(gè)IEnumerable<T>
記錄,這意味著當(dāng)你迭代記錄時(shí),一次只返回一條記錄。這也意味著只有一小部分文件被讀入內(nèi)存。不過要小心。如果你執(zhí)行任何執(zhí)行 LINQ
投影的操作,例如調(diào)用.ToList()
,整個(gè)文件將被讀入內(nèi)存。CsvReader
是只向前的,所以如果你想對(duì)你的數(shù)據(jù)運(yùn)行任何 LINQ
查詢,你必須將整個(gè)文件拉入內(nèi)存。
假設(shè)我們的 CSV 文件名與我們的類屬性略有不同,我們不想讓我們的屬性匹配。
id,name
1,one
2,two
在這種情況下,名稱是小寫的。我們希望我們的屬性名稱是 Pascal 案例,這樣我們就可以更改我們的屬性與標(biāo)題名稱的匹配方式。
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
PrepareHeaderForMatch = args => args.Header.ToLower(),
};
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, config))
{
var records = csv.GetRecords<Foo>();
}
使用配置的 PrepareHeaderForMatch
,我們可以更改標(biāo)頭匹配的方式來匹配屬性名稱。標(biāo)頭和屬性名稱都在函數(shù)中運(yùn)行 PrepareHeaderForMatch
。當(dāng)讀者需要找到要為標(biāo)題設(shè)置的屬性時(shí),它們現(xiàn)在將匹配。你可以使用此功能做其他事情,例如刪除空格或其他字符。
假設(shè) CSV 文件根本沒有標(biāo)題。
1,one
2,two
首先我們需要告訴讀者沒有頭記錄,使用配置。
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false,
};
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, config))
{
var records = csv.GetRecords<Foo>();
}
CsvReader
將使用屬性在類中的位置作為索引位置。但是這有一個(gè)問題,你不能依賴 .NET 中類成員的順序。我們可以通過將屬性映射到 CSV 文件中的某個(gè)位置來解決這個(gè)問題。
一種方法是使用屬性映射。
public class Foo
{
[Index(0)]
public int Id { get; set; }
[Index(1)]
public string Name { get; set; }
}
IndexAttribute
允許你指定要將 CSV 字段用于屬性的哪個(gè)位置。
你也可以按名稱映射,讓我們使用之前的小寫標(biāo)題示例,看看我們?nèi)绾问褂脤傩远皇歉臉?biāo)題匹配。
public class Foo
{
[Name("id")]
public int Id { get; set; }
[Name("name")]
public string Name { get; set; }
}
你還可以使用許多其他屬性,請(qǐng)參閱如下網(wǎng)址。
https://joshclose.github.io/CsvHelper/examples/configuration/attributes
如果我們無(wú)法控制要映射到的類,所以我們無(wú)法向其添加屬性怎么辦?這種情況下,我們可以使用fluentClassMap
來做映射。
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Name("id");
Map(m => m.Name).Name("name");
}
}
要使用映射,我們需要在上下文中注冊(cè)它。
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Context.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
創(chuàng)建類映射是在 CsvHelper 中映射文件的推薦方法,因?yàn)樗鼜?qiáng)大。
你也可以手動(dòng)讀取行。
using (var reader = new StreamReader("path\\to\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var record = csv.GetRecord<Foo>();
// Do something with the record.
}
}
Read
將向前進(jìn)行,ReadHeader
會(huì)將行讀入 CsvHelper 作為標(biāo)題值。ReadHeader
分隔Read
并允許你在移動(dòng)到下一行之前對(duì)標(biāo)題行執(zhí)行其他操作。GetRecord
也不會(huì)提前讓閱讀器允許你對(duì)你可能需要做的行做其他事情。你可能需要GetField
針對(duì)單個(gè)字段或GetRecord
多次調(diào)用以填充多個(gè)對(duì)象。
寫 CSV 文件
現(xiàn)在讓我們看看如何寫入 CSV 文件。它基本上是同一件事,但順序相反。
讓我們使用與以前相同的類定義。
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
我們有一組這樣的記錄。
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 2, Name = "two" },
};
我們可以將記錄寫入文件而無(wú)需任何配置。
using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(records);
}
該WriteRecords
方法會(huì)將所有記錄寫入文件。寫入完成后,你應(yīng)該調(diào)用writer.Flush()
以確保寫入器內(nèi)部緩沖區(qū)中的所有數(shù)據(jù)都已刷新到文件中。一旦一個(gè)using
塊退出,writer
就會(huì)自動(dòng)刷新,所以我們不必在這里明確地這樣做。建議始終用using
塊包裹任何IDisposable
對(duì)象。該對(duì)象將在using
塊退出后盡快處理自身(在我們的例子中也是 flush
) 。
還記得我們?nèi)绾尾荒芤蕾?.NET 中的屬性順序嗎?如果我們正在編寫一個(gè)具有標(biāo)題的類,那沒關(guān)系,只要我們稍后使用標(biāo)題進(jìn)行閱讀即可。如果我們想在 CSV 文件中定位標(biāo)題,我們需要指定一個(gè)索引來保證它的順序。建議在編寫時(shí)始終設(shè)置索引。
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(0).Name("id");
Map(m => m.Name).Index(1).Name("name");
}
}
你也可以手寫行。
using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteHeader<Foo>();
csv.NextRecord();
foreach (var record in records)
{
csv.WriteRecord(record);
csv.NextRecord();
}
}
WriteHeader
不會(huì)讓你前進(jìn)到下一行。 如果需要,NextRecord
從WriteHeader
中分離允許你在標(biāo)題中寫入更多內(nèi)容。WriteRecord
也不會(huì)使你前進(jìn)到下一行,從而使你能夠?qū)懭攵鄠€(gè)對(duì)象或用WriteField
寫入單個(gè)字段。
示例
CsvHelper 讀寫CSV文件的示例,請(qǐng)參閱以下文章:
總結(jié)
本文詳細(xì)介紹了 C# .NET 使用 CsvHelper 讀寫CSV文件的方法。CSVHelper 是在 C# 中使用 CSV 的鼻祖,它根本不需要做任何映射,它自己處理雙引號(hào)、換行和枚舉解析,它可以處理完全自定義的映射、自定義類型轉(zhuǎn)換。此外,它的速度又快又易用,并且還是免費(fèi)的可用于商用用途,真的是一個(gè)讓人驚嘆的庫(kù)。
相關(guān)文章