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

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

有帮助吗?

解决方案

根据我的经验,事后猜测您需要的数据总是浪费时间。重要的是构建你的 元数据 以可扩展的方式。对于 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回溯并读取元数据。根据文件中包含的页脚大小,您可以对缺少的字段使用默认值。

作为 基思·B 指出,您甚至可以使用此技术将元数据存储为 XML 字符串,从而既具有完全可扩展元数据的优点,又具有二进制数据的紧凑性和速度。

其他提示

对于大型二进制文件,我会认真看待HDF5(Google for it)。即使它不是你想要采用的东西,它也可能会指出你在设计自己的格式时有一些有用的方向。

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

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

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

否则我会选择其他人已经说过的内容:文件创建的时间戳,创建它的机器的标识符,基本上您可以考虑用于诊断目的的任何其他内容。理想情况下,您将包含结构格式本身的定义。如果您经常更改结构,那么维护适当版本的代码以阅读各种格式的旧数据文件会非常困难。

@highpercomp提到的HDF5的一大优势是,您只需要担心结构格式的变化,只要您对名称和数据类型有一些约定即可。结构名称和数据类型都存储在文件本身中,因此您可以将C代码吹到smithereens并且无关紧要,您仍然可以从HDF5文件中检索数据。它让你不用担心数据的格式以及更多关于结构的数据,即我不关心字节序列,这是HDF5的问题,但我关心字段名称等。

我喜欢HDF5的另一个原因是你可以选择使用压缩,这需要花费很少的时间,如果数据正在缓慢变化或者大部分相同,除了一些错误的blip之外,你可以在存储空间中获得巨大的胜利有趣的。

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

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

除了架构版本控制所需的任何信息之外,还可以添加在排除问题时可能有价值的详细信息。例如:

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

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

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

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

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

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

  • 公共“长度”字段(包括标头)。
  • 类似于 CRC32(包括标头)。

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

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

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

所以现在不要在标题中添加内容,因为它可能很有趣。您只需创建必须维护的代码,但这些代码几乎没有实际价值。

对于大型文件,您可能希望添加数据定义,因此您的文件格式会自我描述。

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

总结 - 将格式化文本元数据放在文件末尾,以确定其长度存储在别处。

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

2)使用XML或YAML或JSON作为元数据。如果最后附加元数据,这是特别有用/安全的,因为没有人阅读文件会自动认为它只是因为它以XML开头而是所有XML。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top