用C#中的自定义分隔符和一些非常非常大的字段值来解析文本的最快方法是什么?

用C#中的自定义分隔符和一些非常非常大的字段值来解析文本的最快方法是什么?,c#,parsing,bulk,delimited-text,C#,Parsing,Bulk,Delimited Text,我一直在尝试处理一些带有非标准分隔符(不是逗号/引号或制表符分隔符)的分隔文本文件。分隔符是随机的ASCII字符,在分隔符之间不经常显示。在四处搜索之后,我似乎只发现.NET中没有任何解决方案能够满足我的需要,而人们为此编写的自定义库在涉及到巨大的输入时似乎存在一些缺陷(4GB文件,其中一些字段值很容易包含数百万个字符) 虽然这似乎有点极端,但对于某些审阅软件来说,在电子文档发现(EDD)行业中,字段值包含文档的全部内容实际上是一个标准。作为参考,我以前在python中使用csv模块完成了这项工

我一直在尝试处理一些带有非标准分隔符(不是逗号/引号或制表符分隔符)的分隔文本文件。分隔符是随机的ASCII字符,在分隔符之间不经常显示。在四处搜索之后,我似乎只发现.NET中没有任何解决方案能够满足我的需要,而人们为此编写的自定义库在涉及到巨大的输入时似乎存在一些缺陷(4GB文件,其中一些字段值很容易包含数百万个字符)

虽然这似乎有点极端,但对于某些审阅软件来说,在电子文档发现(EDD)行业中,字段值包含文档的全部内容实际上是一个标准。作为参考,我以前在python中使用csv模块完成了这项工作,没有任何问题

下面是一个输入示例:

Field delimiter = 
quote character = þ

þFieldName1þþFieldName2þþFieldName3þþFieldName4þ
þValue1þþValue2þþValue3þþSomeVery,Very,Very,Large value(5MB or so)þ
...etc...
编辑:
所以我继续,从头开始创建了一个分隔文件解析器。我有点厌倦使用这个解决方案,因为它可能容易出现错误。对于这样的任务,必须编写自己的解析器也感觉不到“优雅”或正确。我也有一种感觉,我可能不必为此从头开始编写解析器

虽然这无助于解决较大的输入问题,但解析问题的可能解决方案可能包括一个自定义解析器,该解析器使用策略模式提供分隔符。

使用。它是.NET和开源的。使用编译的IL代码在强类型对象上设置字段具有极高的性能,并支持流式传输

它支持各种文件类型和自定义分隔符;我用它来读取大于4GB的文件

如果出于某种原因,这不适合您,请尝试用字符串逐行阅读。拆分:

public IEnumerable<string[]> CreateEnumerable(StreamReader input)
{
    string line;
    while ((line = input.ReadLine()) != null)
    {
        yield return line.Split('þ');
    }
}

这将跳过标题行,然后打印文件中的前两个字段,其中第四个字段包含字符串“something”。它可以做到这一点,而无需将整个文件加载到内存中。

我倾向于使用内存映射文件()和简单的增量解析的组合,返回到记录/文本行(或任何内容)的IEnumerable列表中。

您提到的一些字段非常大,如果你试图把它们全部读到记忆中,你可能会给自己带来麻烦。我将以8K(或小块)的形式读取文件,解析当前缓冲区,跟踪状态


您试图对正在解析的数据执行什么操作?你在找什么吗?你在改造它吗

Windows和高性能I/O意味着使用端口。你可能需要做一些额外的管道,让它在你的情况下工作

这是基于您希望使用C#/.NET的理解,并根据

18) 不要在托管系统中使用Windows异步过程调用(APC) 代码

我必须用艰苦的方式学习这一点;),但排除使用APC,IOCP是唯一明智的选择。它还支持许多其他类型的I/O,这些I/O通常在套接字服务器中使用


至于解析实际文本,请查看blog,了解一些简化的流使用。

我认为编写自定义解析器没有问题。这些要求似乎与BCL已经提供的任何内容都有很大的不同,所以请继续

“优雅”显然是主观的东西。在我看来,如果解析器的API看起来和工作方式类似于标准的BCL“reader”类型API,那么它就相当“优雅”

对于较大的数据量,让解析器一次读取一个字节,并使用简单的状态机来确定要做什么。将流式处理和缓冲留给底层的
FileStream
类。您应该对性能和内存消耗感到满意

关于如何使用此类解析器类的示例:

using(var reader = new EddReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)) {
    // Read a small field
    string smallField = reader.ReadFieldAsText();
    // Read a large field
    Stream largeField = reader.ReadFieldAsStream();
}

当您需要通过代码而不是数据进行参数化时,“策略模式”非常有用,而且它主要只存在于Java和其他没有闭包的语言中。一个简单的Jane参数将做得很好。如果你认为他可能需要在将来某个时候解析不被分隔的数据(比如说EDI或类似的东西),那么策略是理想的。所有面向对象的纳粹需要停止设计事物。在FielHelpAPI中,我似乎没有办法指定一个引用字符。它似乎还依赖预定义对象来读取每条记录(这是一个问题,因为字段的内容和数量未知)。简单地拆分分隔符并不能切掉它,因为它会忽略引号字符。FileHelpers是开源的,并且绝对支持引号字符。至于拆分引号字符,只需将其添加到string.split!目前主要是对其进行转换(删除数据、重新排序列、验证值等)。将来,我计划使用SQLite进行更复杂的操作。现在,假设一条记录可以放入内存是非常安全的。你说分隔符“不经常出现”在分隔符之间-它们是如何出现的?引用/转义机制是什么?分隔符没有转义字符。如果它们出现在带引号的字段之间,则应将其解释为字段的内容。
using(var reader = new EddReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 8192)) {
    // Read a small field
    string smallField = reader.ReadFieldAsText();
    // Read a large field
    Stream largeField = reader.ReadFieldAsStream();
}