6

我有一个由 C 程序生成的平面文件。文件中的每条记录都包含一个固定长度的标头,后跟数据。标头包含一个指示后续数据大小的字段。我的最终目标是编写一个 C#/.NET 程序来查询这个平面文件,所以我正在寻找使用 C# 读取文件的最有效方法。

我无法在以下代码中找到第 7 行的 .NET 等效项。据我所知,我必须发出多次读取(使用 BinaryReader 为标题的每个字段一个),然后发出一次读取以获取标题后面的数据。我正在尝试学习一种在两次读取操作中解析记录的方法(一次读取以获取固定长度的标头,第二次读取以获取以下数据)。

这是我尝试使用 C#/.NET 复制的 C 代码:

struct header header; /* 1-byte aligned structure (48 bytes) */
char *data;

FILE* fp = fopen("flatfile", "r");
while (!feof(fp))
{
  fread(&header, 48, 1, fp);
  /* Read header.length number of bytes to get the data. */
  data = (char*)malloc(header.length);
  fread(data, header.length, 1, fp);
  /* Do stuff... */
  free(data);
}

这是标头的 C 结构:

struct header
{
    char  id[2];
    char  toname[12];
    char  fromname[12];
    char  routeto[6];
    char  routefrom[6];
    char  flag1;
    char  flag2;
    char  flag3;
    char  flag4;
    char  cycl[4];
    unsigned short len;
};

我想出了这个 C# 对象来表示 C 标头:

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi, Size = 48)]
class RouterHeader
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    char[] Type;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    char[] To;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    char[] From;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    char[] RouteTo;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    char[] RouteFrom;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    char[] Flags;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    char[] Cycle;

    UInt16 Length;
}
4

5 回答 5

2

好吧,您可以使用一次调用来Stream.Read读取长度(尽管您需要检查返回值以确保您已经阅读了您所要求的所有内容;您可能无法一口气全部完成),然后再次调用Stream.Read将数据本身放入一个字节数组中(再次循环,直到您读取任何内容)。一旦它全部在内存中,您就可以从缓冲区中挑选出适当的字节来创建您的结构(或类)的实例。

就我个人而言,我更喜欢明确地做所有这些而不是使用StructLayout——后者总是让我觉得有点脆弱。

于 2010-08-21T17:34:51.057 回答
0

作为替代方案,您可以尝试使用类似联合的结构来创建可以一次性读取的标头结构(例如,作为适当长度的字符串),但随后能够在您使用时引用各个字段来自该结构的信息。

您可以在此处找到有关使用 StructLayouts 和 FieldOffsets 来实现此类事情的更多详细信息。

这里有一些关于用 C# 读写二进制文件的进一步讨论。建议使用 BinaryReader 读取多个字段对于少量(<40)字段通常更有效。

于 2010-08-21T15:52:29.510 回答
0

我建议您只编写代码(每个字段一个语句)来逐个读取字段。这是一些额外的代码,但提供了更多的灵活性。首先,它使您不再需要内存中的数据结构必须与磁盘上的文件具有相同的布局。它可能是另一个结构的一部分,例如,您可以使用String它来代替。char[]

还要考虑:如果您需要编写 2.0 版,在结构的末尾添加一个新字段怎么办?在您的示例中,您需要定义一个新结构,并且您将被这两个定义所困扰。如果您选择代码中的读/写,则可以通过有条件地读取新元素来使用相同的代码支持两者。

于 2010-08-21T19:20:01.750 回答
0

我的倾向是将数据读入一个数组,然后适当地组装数据对象,使用移位和添加来处理单词、长字等。我有一些实用程序类来处理这类事情。

于 2010-08-21T22:10:25.290 回答
0

Hans Passant 提供的链接有答案。我会给他功劳,但我不知道该怎么做,因为他是作为评论而不是答案发布的。

于 2010-08-25T14:26:07.340 回答