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

C# CSV 文件和字符串读取器类

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2014年5月20日

CPOL

5分钟阅读

viewsIcon

58575

downloadIcon

2353

CSVFileReader 和 CSVStringReader 是轻量级且快速的类,类似于单向数据集

引言

考虑到现有免费开源的 CSV 读取器组件/类数量众多,我惊讶地发现由于各种原因,我找不到一个能完全满足需求的:有些读取器需要额外的修改,因为它们不包含我感兴趣的格式;有些不易使用;有些实现的逻辑难以分析等等。

我想一劳永逸地解决“需要 CSV 读取器”的问题,于是决定再提出一个解决方案。最终,我对读取器的主要需求凝聚成以下几点:

  • 能够处理绝大多数(如果不是全部)现有的 CSV 格式变体,包括“制表符分隔”等。
  • 极其直观且易于使用。理想情况下,只需查看 `public` 方法和属性列表就能明白如何使用。
  • 可扩展性,以支持不同的 CSV 数据源。
  • 快速、直接、简洁的解析器,具有最少的条件逻辑。

我相信我在此介绍的基础类(及其派生类)满足上述要求。

`CSVFileReader` 和 `CSVStringReader` 是轻量级且快速的类,类似于单向数据集。它们非常易于使用,并提供了可以处理多种现有 CSV 和“类 CSV”格式的属性。

这些类继承自 `abstract` `CSVReader` 类,该类不指定数据源,而是与 `TextReader` 类的实例进行交互。

`CSVFileReader` 和 `CSVStringReader` 分别接受文件和 `string` 作为数据源。它们引入了额外的“CSV 源”相关属性,并重写了返回特定 `TextReader` 派生类实例的 `abstract` 方法:

protected abstract TextReader CreateDataSourceReader();

可以以类似的方式创建其他 CSV 数据源的类。

输入数据格式 (CSV 格式)

根据 Wikipedia:**“CSV 文件格式没有一个通用的标准,但 RFC 4180 为其中某些方面提供了事实上的标准。”**

虽然此 `CSVReader` 符合 RFC4180 标准,但它提供了许多“额外功能”(请参见下文的 附录 以总结 RFC4180)。

CSVReader 功能

  • 支持三种行分隔符:`<CR>`、`<CR><LF>` 和 `<LF>`,这些分隔符可以同时存在于同一个 CSV 文件中。因此,`<LF><CR>` 对将导致空行,但仍可以通过将 `IgnoreEmptyLines` 属性设置为 `true` 来处理这种情况。
  • 文件第一条记录中是否存在标题由 `bool` 属性 `HeaderPresent` 控制。
  • 可以忽略空行(默认情况下不忽略)。
  • 字段数量是根据第一条记录自动检测的(默认),或者在关闭自动检测时必须显式设置。
  • 字段分隔符默认为逗号 (0x2C),但实际上可以使用任何(Unicode)字符,例如制表符等。
  • 字段引用允许多行字段值以及字段内存在引号和字段分隔符字符。默认情况下,假设字段可能被引号括起来,也可能不被括起来,但可以指示读取器不使用字段引用。
  • 引号字符默认为双引号 (0x22),但实际上可以使用任何(Unicode)字符。假设引号字符也用作转义字符。
  • 默认情况下,假定字符编码范围为 Unicode,但可以通过将相应属性设置为 true 来限制为仅 ASCII。
  • 代码小于 0x20 的字符被视为“特殊字符”,默认情况下不得出现在文件中。此要求不影响行分隔符以及字段分隔符和/或引号字符,如果它们在此范围内。可以选择指示读取器简单地忽略特殊字符。
  • 读取器本身不使用缓冲。它仅使用足够的内存来存储当前记录的字段名称和字段值。如果发生任何缓冲,则由标准的 .NET 类(如 `StreamReader` 和 `StringReader`)负责。
  • 据推测,读取器速度很快,因为它直接从 `TextReader` 读取每个字符,并且只分析一次字符,即读取器进行单次解析。此外,解析器使用的条件逻辑最少。

公共类成员

构造函数

每个类都有一个不带参数的构造函数。

输入属性

在 Active/Open 状态下更改它们的值会导致异常。

通用 (CSVReader) 属性**
bool HeaderPresent
/*- False (by default): All records are considered as records with values.
  - True: Values in the very first record are considered as field names.*/

bool FieldCount_AutoDetect
/*- False: In this situation FieldCount should be set before Open().
  - True (by default): FieldCount auto detection is done during/within Open 
  on the base of very first record (or on the base of first non-empty record 
  when IgnoreEmptyLines==true) and is available after Open()is complete.*/

int FieldCount
/*- FieldCount is always >= 0. Attempt to assign negative value is ignored. 
If FieldCount_AutoDetect is true then assigned value is meaningless and will be replaced during Open().*/

int FieldSeparatorCharCode
/*- It is a code (!) and not a char. Default value is 0x2C (comma). 
Virtually any (see also property ASCIIonly) Unicode character including 
special characters like TAB, etc. can be used as field separator.*/

bool UseFieldQuoting
/*- False: In this case it is assumed that “quote char” is never used 
for field value surrounding and is considered as ordinary data character 
provided that code is in data char code range, i.e. not special character. 
In general, value specified as QuoteCharCode (see below) is meaningless.
  - True (by default): Field value may or may not be enclosed 
  in characters specified in QuoteCharCode (see below).*/

int QuoteCharCode
/*- It is a code (!) and not a char. Default value is 0x22 (double quotes). 
Virtually any (see also property ASCIIonly) Unicode character can be used as “quote char”. 
It is assumed that this character is also used as escape character.*/
           
bool IgnoreEmptyLines
/*- False (by default): Presence of empty lines in source, 
which is indication of wrong input data format, causes exception.
  - True: Empty lines are ignored.*/

bool ASCIIonly
/*- False (by default): Full Unicode range of characters is handled. 
Characters with codes less than 0x20 are considered as “special characters” 
(see property IgnoreSpecialCharacters below).
  - True: Only ASCII range of characters is handled. 
  Characters with codes outside range 0x20 – 0x7E are considered as 
  “special characters” (see property IgnoreSpecialCharacters below).*/

bool IgnoreSpecialCharacters
/*- False (by default): Presence of “special characters”, as they 
defined above in property ASCIIonly description, causes exception. 
This does not affect line breaks, field separator and quote characters 
even if last two are from the “special character” range.
  - True: “Special characters” are ignored except line breaks, 
  field separator and quote characters even if last two are from the 
  “special character” range.*/
CSVFileReader 特有属性
string FileName
//- CSV data file path. 
CSVStringReader 特有属性*
string DataString
//- CSV data string. 

其他通用属性

bool Active
/*- Active indicates current state of the reader. Active is true after successful Open() 
until Close(). Any exception related to reading source data changes state to “inactive” 
(Active == false). Setting Active = true is equivalent to Open() and Active = false is equivalent to Close().*/

bool Bof
//- It is true at the beginning of data source.

bool Eof
//- It is true at the end of data source.

CSVFields Fields
/*- Indexed property that provides access to fields of the current record. 
Each Fields[i] element is an instance of the class CSVField that exposes two properties: 
string Value, which holds value of the field, and string Name, which holds name of the 
field and is always empty if property HeaderPresent is false. 
Field names are available after Open() and until Close().*/

int RecordCountProcessedSoFar
//- Number of records (not lines!) from data source processed at any given moment

方法

void Open()

void Close()

void Next()
/*- Reads next record. Calling Next() after end of file is reached results 
in return of the record with empty field values.*/     

事件

event EventHandler FieldCountAutoDetectCompleted
/*- This event fires from within Open() if FieldCount_AutoDetect is true. 
Use of this event is optional since “auto-detected” FieldCount is 
available upon completion of Open() any way.*/

Using the Code

使用方法很简单。只需创建相应类的实例,指定 CSV 数据源,如有必要修改一些属性,调用 `Open()`,然后迭代记录,调用 `Next()`。在每条记录中,迭代字段值。完成后调用 `Close()`。

使用 CSVFileReader 类

using Nvv.Components.CSV;    //namespace

using (CSVFileReader csvReader = new CSVFileReader())
{
    csvReader.FileName = "CSVFilePath"; // Assign CSV data file path
    // Modify values of other input properties if necessary. For example:
    csvReader.HeaderPresent = true;

    csvReader.Open();

    if (csvReader.HeaderPresent)
        for (int i = 0; i < csvReader.FieldCount; i++)
        {
            // Process field names csvReader.Fields[i].Name. For example:
            Console.WriteLine("Name{0}={1}", i, csvReader.Fields[i].Name);
        }
    while (!csvReader.Eof)
    {
        for (int i = 0; i < csvReader.FieldCount; i++)
        {
            // Process current record's field values csvReader.Fields[i].Value. For example:
            Console.WriteLine("Value{0}={1}", i, csvReader.Fields[i].Value);
        }
        csvReader.Next();
    }

    csvReader.Close(); //Recommended but optional if within "using"
}

使用 CSVStringReader 类

using Nvv.Components.CSV;    //namespace

using (CSVStringReader csvReader = new CSVStringReader())
{
    csvReader.DataString = "1,2,3"; // Assign string containing CSV data
    // Modify values of other input properties if necessary. For example:
    csvReader.HeaderPresent = true;

    csvReader.Open();

    if (csvReader.HeaderPresent)
        for (int i = 0; i < csvReader.FieldCount; i++)
        {
            // Process field names csvReader.Fields[i].Name. For example:
            Console.WriteLine("Name{0}={1}", i, csvReader.Fields[i].Name);
        }
    while (!csvReader.Eof)
    {
        for (int i = 0; i < csvReader.FieldCount; i++)
        {
            // Process current record's field values csvReader.Fields[i].Value. For example:
            Console.WriteLine("Value{0}={1}", i, csvReader.Fields[i].Value);
        }
        csvReader.Next();
    }
    csvReader.Close(); //Recommended but optional if within "using"
}

下载源代码

以下源代码是用 Visual C# 2010 Express 准备的,可供下载:

  • 包含 `CSVReader`、`CSVFileReader` 和 `CSVStringReader` 类的 C# 解决方案和程序集项目位于 `CSVClasses` 文件夹中。
  • 测试 `CSVFileReader` 和 `CSVStringReader` 类的应用程序的 C# 解决方案位于 `CSVTest` 文件夹中。此解决方案还包括并引用了上述 `CSVClasses` 程序集项目。如果对此应用程序感兴趣,为了避免引用中断,最好将所有内容解压缩在一起,就像在 zip 文件中一样。

两个解决方案都以 .NET 4.0 为目标,尽管至少 `CSVClasses` 很可能可以“重新定位”到其他版本。

附录

关于 RFC 4180 对 CSV 格式定义的简要总结(http://tools.ietf.org/html/rfc4180

  • 每条记录都位于单独的行上,由换行符分隔(`<CR><LF> = 0x0D 0x0A`),除了最后一条记录,此处可省略。
  • 可选的标题(带有字段名)可以作为第一条记录出现。
  • 每条记录在整个文件中应包含相同数量的字段。这实际上不允许空行,除非 CSV 文件只有一个字段,在这种情况下,它只包含一个“空”值。
  • 字段分隔符是逗号 (0x2C)。
  • 字段可以被双引号 (0x22) 包围,也可以不被包围。如果被包围,则允许字段中包含换行符、双引号和逗号。双引号也用作转义字符。
  • 空格被视为字段的一部分,不应被忽略。
  • 字段中可以出现的文本数据限于代码范围 0x20 – 0x7E(这显然将其限制为 ASCII 码)。

历史

版本 1.1 (2014-09-03)

1. 命名空间已更改

2. 显著的性能提升

  • 在适当的地方使用 StringBuilder。
  • 将频繁调用的方法/过程组合成一个大过程,以牺牲代码结构和可读性为代价。显然,过程调用的时间很重要。

版本 1.0 (2014-05-20)

  • 首次发布
© . All rights reserved.