【问题标题】:C#/.NET - Custom Binary File Formats - Where to Start?C#/.NET - 自定义二进制文件格式 - 从哪里开始?
【发布时间】:2009-04-27 19:40:15
【问题描述】:

我需要能够以自定义二进制文件格式存储一些数据。我以前从未设计过自己的文件格式。它需要是一种在 C#、Java 和 Ruby/Perl/Python 世界之间穿梭的友好格式。

从文件开始将包含记录。一个 GUID 字段和一个 JSON/YAML/XML 数据包字段。我不确定用什么作为分隔符。逗号、制表符或换行符之类的东西似乎太脆弱了。 Excel 是做什么的?还是 XML 之前的 OpenOffice 格式?您应该使用 ASCII 字符 0 还是 1。不知道从哪里开始。有没有关于该主题的文章或书籍?

此文件格式稍后可能会扩展为包含“标题部分”。

注意:首先我将在 .NET 中工作,但我希望该格式易于移植。

更新:
“数据包”的处理可能很慢,但文件格式内的导航却不能。所以我认为 XML 是不可能的。

【问题讨论】:

  • 重新编辑:这里的用例是什么?在许多情况下,您选择不导航 inin 文件,而是将其反序列化为对象模型,然后在其中工作。还有更多,您不妨使用某种(常见)类型的数据库文件。
  • 我应该添加这个文件将是大的序列化。所以我永远不想一次将所有数据都保存在内存中。它可能是 List 序列化的,但我需要一个分隔符,因此我不必一次读取整个列表。

标签: .net file binary file-format binary-data


【解决方案1】:

看看使用“协议缓冲区”怎么样?设计为一种高效、可移植、版本容错的通用二进制格式,它为您提供google library 中的 C++、Java 和 Python,以及 community ports 中的 C#、Perl、Ruby 和其他?

请注意,Guid 没有特定的数据类型,但您可以使用(基本上)byte[] 将其作为消息填充。

通常对于 .NET 工作,我推荐 protobuf-net(但作为作者,我有些偏见) - 但是,如果您打算以后使用其他语言,您可能会做得更好(长期)使用 Jon 的dotnet-protobufs;这将为您提供跨平台的熟悉 API(其中 protobuf-net 使用 .NET 习惯用法)。

【讨论】:

  • 还有 Python - 这是 Google 直接提供的语言之一。
  • 我想知道我是否应该依赖协议缓冲区的东西(即使它是 Apache 许可证)。或者我应该从 Protocol Buffers 的工作中了解二进制文件格式。我想我已经开始依赖 Json.NET 和 MIT 许可证了。
【解决方案2】:

我将尝试添加一些创建可移植二进制文件格式的一般提示。

请注意,发明二进制文件格式意味着记录其中的位必须如何传输以及它们的含义。不是编码,而是文档。

现在提示:

  1. 决定如何处理 endianess。好的和简单的方法是一劳永逸地决定它。在普通 PC(即 x86)上使用时,最好选择小端,以节省转换(性能)。

  2. 创建标题。是的,总是有一个标题是个好主意。文件的第一个字节应该能够告诉你,你在搞什么格式。

    • 从能够识别格式的魔法开始(ASCII 字符串可以解决问题)
    • 添加版本。添加文件格式的版本不会有什么坏处,并且可以让您以后进行向后兼容。
  3. 最后,添加数据。现在,数据的格式将是特定的,它将始终基于您的确切需求。基本上,数据将存储在某种数据结构的二进制映像中。数据结构是你需要想出来的。

如果您需要通过某种索引随机访问您的数据,B-Trees 是不错的选择,而如果您只需要大量的数字来将它们全部写入然后读取它们,那么“数组”就可以了把戏。

此外,您可以使用TLV (Type-Length-Value) 概念来实现前向兼容性。

【讨论】:

  • 关于在文件格式内的“页面”上建立我的知识有什么建议吗?我应该阅读的文章或书籍?
  • 当我说“页面”时,我的意思是像数据库页面。 SQLite 有点难以遵循 C 代码。也许我可以更清楚地遵循 Java 或 C# 示例。
【解决方案3】:

ASCII 字符 0 或 1 每个占用几个位(就像任何其他字符一样),所以如果你这样存储它,你的“二进制”文件将比​​它应该的大几倍。在零和一的文本文件不完全是二进制文件:)

您可以使用BinaryWriter 将原始数据直接写入file stream。您需要弄清楚的唯一部分是将您的内存格式(通常是某种对象图)转换为 BinaryWriter 可以使用的字节序列。

然而,如果您的主要兴趣是可移植性,我建议您完全不要使用二进制格式。 XML 正是为解决可移植性和互操作性问题而设计的。作为一种文件格式,它既冗长又繁重,但这是您为解决这些问题而做出的权衡。如果无法使用人类可读的格式,那么Marc's answer 是您的最佳选择。无需重新发明便携轮!

【讨论】:

  • 没有必要为了获得可移植性而牺牲速度和大小 - 请参阅 Marc 的 Protocol Buffers 答案。您失去了人类可读性(在编码形式时 - 您可以将 PB 转储为文本)并且您需要预先指定结构,但您可以免费获得大小、速度和向后/向前兼容性。
  • 你提出了一个关于 ASCII 注释的好观点。大多数人如何以二进制格式分隔字符串的开头或结尾?我知道我的 GUID 将具有标准长度,但我的“数据包数据”将基于字符串。我听说过“空终止”字符串这个术语。那是什么?表明我缺乏适当的 CS 学位。
  • @Jon Skeet 这是一个好点。对我来说,protocol buffers 和 XML 这样的人类可读格式之间的问题只是人们需要的可移植性、灵活性和开放性的程度。我的专业经验趋向于需要非常开放的格式,所以我总是首先推荐一些 XML-ish :)
  • @Tundall - 对于字符串/数组数据,最好的办法是为数据加上大小前缀。然后,如果您不需要它,您可以跳过它。另一种方法是使用一些特殊的标记(例如 0,不会出现在常规文本中)作为结尾 - 当然,您不能在二进制数据(例如 guid)中使用它,因为 0 是完全有效的和预期的二进制值。所以长度前缀成为最好的选择。
  • 例如(来自协议缓冲区编码文档)- 12 07 74 65 73 74 69 6e 67 表示“字段2作为字符串”(12)“7字节”(07),“测试”(UTF8 中的其余数据)。我不会尝试解释“12”,或者长字符串(需要超过 1 个字节来指定长度)会发生什么 - 但它都已明确定义。
【解决方案4】:

这取决于您将写入二进制文件的数据类型以及二进制文件的用途。它们是类对象还是只是记录数据?如果是记录数据,我建议将其以 xml 格式。这样,您可以包含模式验证以验证文件是否符合您的标准。在 java 和 .NET 中都有工具可以从 / 到 xml 格式导入和导出数据。

【讨论】:

    【解决方案5】:

    假设你的格式是:

        struct Format
        {
            struct Header // 1
            {
                byte a;
                bool b1, b2, b3, b4, b5, b6, b7, b8;
                string name;
            }
            struct Container // 1...*
            {
                MyTypeEnum Type;
                byte[] data;
            }
        }
    
        enum MyTypeEnum
        {
            Sound,
            Video,
            Image
        }
    

    然后我会有一个顺序文件:


    字节 // 一个

    字节 // b

    int //名称大小

    char[] // 名称(具有上面指定的大小,记住 .NET 中的 char 是 16 位)

    int // MyTypeEnum 类型

    int //数据大小

    byte[] // 数据(具有上面指定的大小)


    然后你可以随意重复最后三行。

    要阅读,请使用BinaryReader,它支持读取字节、整数和字节序列。还有一个BinaryWriter

    此外,请记住 Microsoft .NET(因此在 Windows/Intel 机器上)是 little-endian。 BinaryReaderBinaryWriter 也是如此。

    【讨论】:

    • 查看我在此线程上关于文件大小的其他评论。我想我了解 BinaryReader/Writer,但这是否可以让我一次浏览一下文件?我不需要一次全部反序列化这个东西吗?
    • BinaryReader/BinaryWriter 只是任何 .NET Stream 的助手。它是无缓冲的,因此您只需转到 BaseStream 并寻找您希望 BinaryReader 读取或 BinaryWriter 写入的位置。 FileStream 支持向前和向后搜索。因此,在标题中的某处有一个索引可能会帮助您只读取索引,然后寻找您想要阅读的位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 1970-01-01
    相关资源
    最近更新 更多