.Net 尚未提供读取 CSV 的标准库。
虽然CSV specification比较简单,
用多行数据解析 csv 并非易事。
有些人"cheat"用正则表达式,
但是你需要将整个文件读入字符串,
由于正则表达式不能按需引入更多行,
你仍然需要检测和处理换行符。
那是在我们衡量它的性能、一致性或your new problem之前。
标准建议是使用经过良好测试的解析器包。
CsvHelper 非常全面,如果您只想读取原始数据,我建议您使用 NReco.Csv。
话虽如此,有时您可能不喜欢套餐或选项受限。
不管是什么原因,我已经用一些静态方法编写了一个 csv 解析器,您可以将它们复制并粘贴到您的项目中并开始运行。
用法:
using ( var r = new StreamReader( filePath, Encoding.UTF8, true ) ) {
while ( r.TryReadCsvRow( out var row ) ) {
foreach ( string cell in row ) {
// Your code here.
}
}
}
using ( var r = new StringReader( csvString ) ) {
while ( r.TryReadCsvRow( out var row ) ) {
string[] cells = row.ToArray();
// `cells` is reusable and random-accessible
}
}
解析器代码:
/**
* <summary>Try read a csv row from a Reader. May consume multiple lines. Linebreaks in cells will become \n</summary>
* <param name="source">Reader to get line data from.</param>
* <param name="row">Cell data enumeration (forward-only), or null if no more rows.</param>
* <param name="quoteBuffer">Thread-local buffer for quote parsing. If null, one will be created on demand.</param>
* <returns>True on success, false on no more rows.</returns>
* <see cref="StreamReader.ReadLine"/>
*/
public static bool TryReadCsvRow ( this TextReader source, out IEnumerable<string> row, StringBuilder quoteBuffer = null ) {
row = ReadCsvRow( source, quoteBuffer );
return row != null;
}
/**
* <summary>Read a csv row from a Reader. May consume multiple lines. Linebreaks in cells will become \n</summary>
* <param name="source">Reader to get line data from.</param>
* <param name="quoteBuffer">Thread-local buffer for quote parsing. If null, one will be created on demand.</param>
* <returns>Cell data enumeration (forward-only), or null if no more rows.</returns>
* <see cref="StreamReader.ReadLine"/>
*/
public static IEnumerable<string> ReadCsvRow ( this TextReader source, StringBuilder quoteBuffer = null ) {
var line = source.ReadLine();
if ( line == null ) return null;
return ReadCsvCells( source, line, quoteBuffer );
}
private static IEnumerable<string> ReadCsvCells ( TextReader source, string line, StringBuilder buf ) {
for ( var pos = 0 ; line?.Length >= pos ; )
yield return ReadCsvCell( source, ref line, ref pos, ref buf );
}
private static string ReadCsvCell ( TextReader source, ref string line, ref int pos, ref StringBuilder buf ) {
var len = line.Length;
if ( pos >= len ) { // EOL
pos = len + 1;
return "";
}
// Unquoted cell.
if ( line[ pos ] != '"' ) {
var end = line.IndexOf( ',', pos );
var head = pos;
// Last cell in this row.
if ( end < 0 ) {
pos = len + 1;
return line.Substring( head );
}
// Empty cell.
if ( end == pos ) {
pos++;
return "";
}
pos = end + 1;
return line.Substring( head, end - head );
}
// Quoted cell.
if ( buf == null )
buf = new StringBuilder();
else
buf.Clear();
var start = ++pos; // Drop opening quote.
while ( true ) {
var end = pos < len
? line.IndexOf( '"', pos )
: -1;
var next = end + 1;
// End of line. Append and read next line.
if ( end < 0 ) {
buf.Append( line, start, len - start );
if ( ( line = source.ReadLine() ) == null )
return buf.ToString();
buf.Append( '\n' );
start = pos = 0; len = line.Length;
// End of cell.
} else if ( next == len || line[ next ] == ',' ) {
pos = end + 2;
return buf.Append( line, start, end - start ).ToString();
// Two double quotes.
} else if ( line[ next ] == '"' ) {
buf.Append( line, start, end - start + 1 );
pos = start = end + 2;
// One double quote not followed by EOL or comma.
} else
pos++;
}
}
优点
- 低开销,适用于大文件。 (例如 800mb 的人口普查数据)
- 适用于所有换行符,解析引用的单元格。
- 提高报价解析速度的可选缓冲区。
- 线程安全,如果缓冲区未在线程之间共享。没有锁定。
- 无依赖性。没有Nuget。适用于所有现代 .Net。
缺点
- 所有换行符都将转换为\n。
- 输出是只进/使用一次。使用
ToArray 或ToList 求解。
- 缓冲区(如果提供)在读取后不会被清除。