65.9K
CodeProject 正在变化。 阅读更多。
Home

C# - 轻量级且快速的 CSV 解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (14投票s)

2014年9月27日

CPOL
viewsIcon

88437

轻量级但功能完善的 CSV 解析器,支持自定义分隔符和限定符,并使用 yield 返回记录。

引言

解析 CSV 文件听起来可能是一项简单的任务,但实际上并非如此。以下是一个 CsvParser 类实现,我将其用于我自己的项目中。它支持我认为至关重要的以下功能:

  • 自定义分隔符和限定符字符
  • 支持引用符号(允许分隔符字符成为值的一部分)
  • 支持引用转义(允许引用字符成为值的一部分)
  • 支持 '\n' 和 '\r\n' 两种换行符
  • 设计为通过 yield return 返回 IEnumerable(无内存缓冲区)
  • 设计为分别返回 Header 和其余行(使用 Tuple

源代码

public static class CsvParser
{
    private static Tuple<T, IEnumerable<T>> HeadAndTail<T>(this IEnumerable<T> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        var en = source.GetEnumerator();
        en.MoveNext();
        return Tuple.Create(en.Current, EnumerateTail(en));
    }

    private static IEnumerable<T> EnumerateTail<T>(IEnumerator<T> en)
    {
        while (en.MoveNext()) yield return en.Current;
    }

    public static IEnumerable<IList<string>> 
           Parse(string content, char delimiter, char qualifier)
    {
        using (var reader = new StringReader(content))
            return Parse(reader, delimiter, qualifier);
    }

    public static Tuple<IList<string>, IEnumerable<IList<string>>> 
           ParseHeadAndTail(TextReader reader, char delimiter, char qualifier)
    {
        return HeadAndTail(Parse(reader, delimiter, qualifier));
    }

    public static IEnumerable<IList<string>> 
           Parse(TextReader reader, char delimiter, char qualifier)
    {
        var inQuote = false;
        var record = new List<string>();
        var sb = new StringBuilder();

        while (reader.Peek() != -1)
        {
            var readChar = (char) reader.Read();

            if (readChar == '\n' || (readChar == '\r' && (char) reader.Peek() == '\n'))
            {
                // If it's a \r\n combo consume the \n part and throw it away.
                if (readChar == '\r')
                    reader.Read();

                if (inQuote)
                {
                    if (readChar == '\r')
                        sb.Append('\r');
                    sb.Append('\n');
                }
                else
                {
                    if (record.Count > 0 || sb.Length > 0)
                    {
                        record.Add(sb.ToString());
                        sb.Clear();
                    }

                    if (record.Count > 0)
                        yield return record;

                    record = new List<string>(record.Count);
                }
            }
            else if (sb.Length == 0 && !inQuote)
            {
                if (readChar == qualifier)
                    inQuote = true;
                else if (readChar == delimiter)
                {
                    record.Add(sb.ToString());
                    sb.Clear();
                }
                else if (char.IsWhiteSpace(readChar))
                {
                    // Ignore leading whitespace
                }
                else
                    sb.Append(readChar);
            }
            else if (readChar == delimiter)
            {
                if (inQuote)
                    sb.Append(delimiter);
                else
                {
                    record.Add(sb.ToString());
                    sb.Clear();
                }
            }
            else if (readChar == qualifier)
            {
                if (inQuote)
                {
                    if ((char) reader.Peek() == qualifier)
                    {
                        reader.Read();
                        sb.Append(qualifier);
                    }
                    else
                        inQuote = false;
                }
                else
                    sb.Append(readChar);
            }
            else
                sb.Append(readChar);
        }

        if (record.Count > 0 || sb.Length > 0)
            record.Add(sb.ToString());

        if (record.Count > 0)
            yield return record;
    }
}

Using the Code

这是一个读取 CSV 文件的示例。以下代码片段解析前 5 条记录,并将它们以键/值对的形式打印到 Console 中:

const string fileName = @"C:\Temp\file.csv";
using (var stream = File.OpenRead(fileName))
using (var reader = new StreamReader(stream))
{
    var data = CsvParser.ParseHeadAndTail(reader, ',', '"');

    var header = data.Item1;
    var lines = data.Item2;

    foreach (var line in lines.Take(5))
    {
        for (var i = 0; i < header.Count; i++)
            if (!string.IsNullOrEmpty(line[i]))
                Console.WriteLine("{0}={1}", header[i], line[i]);
        Console.WriteLine();
    }
}
Console.ReadLine();

历史

  • 2014 年 9 月 27 日:初始版本
© . All rights reserved.