10

我有一个模拟,可以读取我们创建的大型二进制数据文件(10 到 100 GB)。出于速度原因,我们使用二进制文件。这些文件是系统相关的,是从我们运行的每个系统上的文本文件转换而来的,所以我不关心可移植性。这些文件目前是许多使用 fwrite 编写的 POD 结构实例。

我需要更改结构,因此我想添加一个包含文件版本号的标头,该标头将在结构更改时递增。由于我正在这样做,我还想添加一些其他信息。我在考虑结构的大小、字节顺序,也许还有创建二进制文件的代码的 svn 版本号。还有什么有用的补充吗?

4

12 回答 12

14

以我的经验,事后猜测你需要的数据总是浪费时间。重要的是以可扩展的方式构建元数据。对于 XML 文件,这很简单,但二进制文件需要更多的思考。

我倾向于将元数据存储在文件末尾的结构中,而不是开头。这有两个优点:

  • 很容易检测到截断/未终止的文件。
  • 元数据页脚通常可以附加到现有文件中,而不会影响其阅读代码。

我使用的最简单的元数据页脚如下所示:

struct MetadataFooter{
  char[40] creatorVersion;
  char[40] creatorApplication;
  .. or whatever
} 

struct FileFooter
{
  int64 metadataFooterSize;  // = sizeof(MetadataFooter)
  char[10] magicString;   // a unique identifier for the format: maybe "MYFILEFMT"
};

在原始数据之后,写入元数据页脚,然后写入文件页脚。

读取文件时,寻找到最后 - sizeof(FileFooter)。阅读页脚,并验证 magicString。然后,根据 metadataFooterSize 回溯,读取元数据。根据文件中包含的页脚大小,您可以使用缺失字段的默认值。

正如KeithB指出的那样,您甚至可以使用这种技术将元数据存储为 XML 字符串,从而既具有完全可扩展的元数据的优点,又具有二进制数据的紧凑性和速度。

于 2009-01-06T13:59:16.847 回答
7

对于大型二进制文件,我会认真研究 HDF5(谷歌)。即使它不是您想要采用的东西,它也可能为您在设计自己的格式时指明一些有用的方向。

于 2009-01-06T15:22:48.033 回答
4

对于大型二进制文件,除了版本号之外,我倾向于添加记录计数和 CRC,原因是大型二进制文件比较小的二进制文件更容易随着时间或在传输过程中被截断和/或损坏。我最近发现 Windows 根本不能很好地处理这个问题,因为我使用资源管理器将大约 2TB 的数百个文件复制到连接的 NAS 设备,并发现每个副本上的 2-3 个文件已损坏(不完全复制)。

于 2009-01-06T13:20:42.430 回答
3

如果您稍后将其他结构写入二进制文件,则文件类型的标识符将很有用。也许这可能是一个短字符串,因此您可以通过查看文件(通过十六进制编辑器)查看它包含的内容。

于 2009-01-06T13:11:25.180 回答
3

如果它们那么大,我会在文件开头保留一个健康的空间块(64K?),并将元数据以 XML 格式放在那里,后跟一个文件结尾字符(Ctrl-Z for DOS/ Windows,Unix 的 ctrl-D?)。这样,您就可以使用现有的各种 XML 工具集轻松检查和解析元数据。

否则,我会使用其他人已经说过的内容:文件创建的时间戳,创建它的机器的标识符,基本上是您可以想到的用于诊断目的的任何其他内容。理想情况下,您将包括结构格式本身的定义。如果您经常更改结构,那么维护正确版本的代码以读取各种格式的旧数据文件是一件很痛苦的事情。

正如@highpercomp 所提到的,HDF5 的一大优势是,只要您对名称和数据类型有一些约定,您就不必担心结构格式的变化。结构名称和数据类型都存储在文件本身中,因此您可以将 C 代码炸成碎片,没关系,您仍然可以从 HDF5 文件中检索数据。它让你更少担心数据的格式,而更多地担心数据的结构,即我不关心字节顺序,这是 HDF5 的问题,但我关心字段名称等。

我喜欢 HDF5 的另一个原因是您可以选择使用压缩,这需要非常短的时间,并且如果数据变化缓慢或几乎相同,除了一些有趣的错误亮点之外,它可以为您带来巨大的存储空间优势。

于 2009-01-06T16:17:19.453 回答
2

@rstevens 说“文件类型的标识符”......合理的建议。按照惯例,这被称为幻数,在文件中,它不是滥用术语(与代码中的滥用术语不同)。基本上,它是一个数字——通常至少 4 个字节,我通常确保这些字节中至少有一个不是 ASCII——您可以使用它来验证文件是否属于您期望的类型,并且混淆的可能性很小. 您还可以在 /etc/magic(或本地等效项)中编写规则来报告包含您的幻数的文件是您的特殊文件类型。

您应该包含文件格式版本号。但是,我建议不要使用代码的 SVN 编号。当文件格式不变时,您的代码可能会更改。

于 2009-01-06T13:25:08.350 回答
1

除了架构版本控制所需的任何信息之外,添加对问题进​​行故障排除时可能有价值的详细信息。例如:

  • 文件创建和更新时间的时间戳(如果适用)。
  • 构建中的版本字符串(理想情况下,您有一个在每个“官方”构建中自动递增的版本字符串......这与文件架构版本不同)。
  • 创建文件的系统名称,以及可能与您的应用相关的其他统计信息

我们发现这非常有用 (a) 获取我们原本必须要求客户提供的信息和 (b) 获取正确的信息 - 令人惊讶的是,有多少客户报告他们正在运行不同版本的软件。数据索赔!

于 2009-01-06T13:18:39.393 回答
1

您可能会考虑将文件偏移量放在标题中的固定位置,它会告诉您实际数据在文件中的开始位置。这将允许您在需要时更改标题的大小。

在某些情况下,我将值 0x12345678 放入标题中,以便检测文件格式是否与正在处理它的机器的字节序匹配。

于 2009-01-06T16:03:10.870 回答
1

正如我在电信设备配置和固件升级方面的经验所表明的那样,您实际上只需要几个从版本(标题的固定部分)开始的预定义字节(这很重要)。标题的其余部分是可选的,通过指示正确的版本,您始终可以显示如何处理它。这里重要的是您最好将标题的“可变”部分放在文件末尾。如果您在不修改文件内容本身的情况下计划对标头进行操作。这也简化了应该重新计算可变标题部分的“附加”操作。

很高兴拥有固定大小标题的功能(在开始时):

  • 通用“长度”字段(包括标题)。
  • CRC32 之类的东西(包括标头)。

好的,对于可变部分 XML 或标题中一些相当可扩展的格式是个好主意,但它真的需要吗?我在 ASN 编码方面有很多经验……在大多数情况下,它的使用是过度的。

好吧,当您查看诸如RFC 2126(第 4.3 章)中描述的 TPKT 格式之类的内容时,也许您会有更多的理解。

于 2013-04-19T06:32:02.923 回答
0

如果您在标头中放置版本号,则可以在需要更改 POD 结构或向标头添加新字段时更改该版本。

所以现在不要在标题中添加东西,因为它可能很有趣。您只是在创建必须维护的代码,但没有什么实际价值。

于 2009-01-06T13:12:06.457 回答
0

对于大文件,您可能想要添加数据定义,这样您的文件格式就可以自我描述了。

于 2009-01-06T13:51:01.153 回答
0

我的变体结合了 Roddy 和 Jason S 的方法。

总而言之 - 将格式化的文本元数据放在文件的末尾,以确定其存储在其他地方的长度。

1)在文件的开头放置一个长度字段,以便您知道最后的元数据长度,而不是假设一个固定长度。这样,要获取元数据,您只需读取该固定长度的初始字段,然后从文件末尾获取元数据 blob。

2) 使用 XML 或 YAML 或 JSON 作为元数据。如果在末尾附加元数据,这将特别有用/安全,因为读取文件的人不会自动认为它都是 XML,因为它以 XML 开头。

这种方法的唯一缺点是当您的元数据增长时,您必须同时更新文件的头部和尾部,但很可能其他部分无论如何都会更新。如果它只是像上次访问日期一样更新琐事,那么元数据长度不会改变,所以它只需要就地更新。

于 2013-04-19T06:10:41.823 回答