3

我一直在尝试处理一些具有非标准分隔符(不是逗号/引号或制表符分隔)的分隔文本文件。定界符是不经常出现在定界符之间的随机 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...

编辑:所以我从头开始创建了一个分隔文件解析器。我有点厌倦使用这个解决方案,因为它可能容易出现错误。为这样的任务编写自己的解析器也不会让人觉得“优雅”或正确。我也有一种感觉,我可能不必为此从头开始编写解析器。

4

6 回答 6

5

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

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

如果由于某种原因不适合您,请尝试使用 string.split 逐行阅读:

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

这将为您提供简单的字符串数组,以流的方式表示行,您甚至可以将其 Linq 插入;)但是请记住,IEnumerable 是延迟加载的,因此在您迭代之前不要关闭或更改 StreamReader(或导致像 ToList/ToArray 之类的完全加载操作 - 但是考虑到您的文件大小,我假设您不会那样做!)。

这是一个很好的使用示例:

using (StreamReader sr = new StreamReader("c:\\test.file"))
{
    var qry = from l in CreateEnumerable(sr).Skip(1)
              where l[3].Contains("something")
              select new { Field1 = l[0], Field2 = l[1] };
    foreach (var item in qry)
    {
        Console.WriteLine(item.Field1 + " , " + item.Field2);
    }
}
Console.ReadLine();

这将跳过标题行,然后从文件中打印出前两个字段,其中第四个字段包含字符串“something”。它会在不将整个文件加载到内存的情况下执行此操作。

于 2008-12-04T03:08:03.493 回答
1

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

这是基于您想要使用 C#/.NET 的理解,并且根据Joe Duffy的说法

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

我不得不以艰难的方式学习这一点;),但排除了 APC 的使用,IOCP 是唯一明智的选择。它还支持许多其他类型的 I/O,经常用于套接字服务器。

至于解析实际文本,请查看Eric White 的博客以了解一些简化的流使用。

于 2009-09-30T11:53:34.193 回答
0

我倾向于使用内存映射文件(此处 msdn 指向 .NET 包装器)和简单的增量解析的组合,返回到记录/文本行(或其他)的 IEnumerable 列表

于 2008-12-04T03:15:37.490 回答
0

您提到某些领域非常大,如果您尝试将它们全部阅读以记忆,您可能会遇到麻烦。我会以 8K(或小块)读取文件,解析当前缓冲区,跟踪状态。

你想用你正在解析的这些数据做什么?你在寻找什么吗?你在改造它吗?

于 2008-12-04T05:46:58.290 回答
0

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

“优雅”显然是一个主观的东西。在我看来,如果你的解析器的 API 看起来和工作起来都像标准的 BCL“阅读器”类型的 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();
}
于 2009-09-30T12:09:53.310 回答
-1

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

于 2008-12-04T03:04:04.533 回答