序列化格式的比较
当您想要保存、发送或接收应用程序中传递的数据时,有许多不同的序列化格式可供选择。哪种格式最适合您的用例?本页旨在通过比较当今使用的一些最流行的序列化格式来帮助回答这个问题。
将比较以下序列化格式:
- CSV
- JSON
- Protobuf(Protocol Buffers协议缓冲区)
- TOML
- XML
- YAML
让我们从一个免责声明开始:没有一种适合所有情况的序列化格式——最适合作业的格式取决于诸如被序列化的数据类型/数量以及将读取它的软件等因素。
以下部分中的示例展示了不同的格式如何存储相同的数据。我选择了一种简单、可重复的数据结构,所有经过审查的序列化格式都支持该结构(两个Pokemon对象,每个对象都有id、name和字段)。请注意,这些序列化格式中age的address许多格式都可以存储不可重复的随机结构化数据(事实上,除了 CSV 之外,所有格式都可以)。因此,在某种程度上,我是在根据最低公分母进行量身定制,这会影响结果。
在每个review部分中,1-3 之间的分数以红色突出显示,4-6 以橙色突出显示,6-10 以绿色突出显示。
CSV
CSV 非常适合以人类可读的格式存储大量表格数据。它不太适合存储对象或哈希表之类的数据结构(与本文介绍的所有其他序列化格式不同)。
CSV 的标准化程度并不高。RFC 4180曾试图将格式标准化,但“CSV”这个名称可能指的是由非逗号字符(如空格、制表符或分号)分隔的文件。事实上,它曾经被称为分隔符分隔值 (DSV),尽管不幸的是,CSV 似乎是如今更流行的术语。分隔符之间的数据格式完全由用户定义(例如,没有指定定义日期的方式)。
CSV 格式允许可选的标题行作为文件的第一行出现。如果存在,它包含记录中每个值的字段名称。此标题行对于标记数据非常有用,几乎应该始终存在。
CSV 格式得到了很好的支持,几乎所有流行的编程语言都有 CSV 库。流行的pandas
Python数据操作库Dataframe
只需使用简单的一行命令即可将 CSV 直接读入数据表(称为)pd.read_csv('myfile.csv')
。CSV 也是本页评论的唯一一种在 Excel 等电子表格程序中得到良好支持的序列化格式(它是唯一一种可以读入 Excel 的序列化格式,因为 CSV 强制使用表格结构)。
对于人类可读的格式,CSV 非常简洁(有关详细信息,请参阅文件大小部分)。但是,很难确定哪列是哪列,尤其是当有大量行(文件顶部只有一个标题列)、有大量列(没有要求列间距相等,因此您最终从左侧开始计算逗号)和/或如果有空字段(即,,
)。
例子
id, name, age, address
4, Charmander, 12.34, Fire St
25, Pikachu, 56.78, Electric St
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 9/10 | 由于 CSV 仅用逗号分隔值,因此是一种非常简洁且易于人类阅读的格式。 |
人类可读性 | 5/10 | CSV 是可读的,尽管在包含大量数据的行中很容易丢失。 |
语言支持 | 9/10 | CSV 得到广泛支持,几乎每种主流语言都可读取(如果不支持,自己编写解析器也很容易!)。电子表格程序也喜欢 CSV 文件。 |
数据结构支持 | 3/10 | CSV 仅支持表格/数组类数据。它不支持字典/地图类数据,也不支持关系数据。 |
速度 | 10/10 | CSV 的序列化/反序列化速度非常快。在序列化/反序列化 10,000 个小对象时,CSV 甚至击败了 Python 中的 Protobuf,在 C++ 中紧随 Protobuf 之后。有关更多信息,请参阅速度比较部分。 |
标准化 | 3/10 | CSV 尚未很好地标准化。 |
JSON
JSON 是一种普遍存在的、可读的数据序列化格式,几乎所有流行的编程语言都支持该格式。JSON 的数据结构与许多语言中的常见对象非常相似,例如 Pythondict可以用 JSON 表示object,Python 也可以list用 JSON 表示array。请注意,这有一些注意事项!
需要注意的是,JSON 语法不支持注释!这可能是一件好事(但更多时候是件坏事)。对于读入软件并写回磁盘的大量数据,注释几乎毫无用处,因为它们在重新写入文件时不会被保留。如果您确实需要注释,常见的解决方法是添加名称comment/值对或类似于 JSON 对象的内容。JSON 对象的名称/值对中的名称始终必须是字符串。它也不支持任何类型的日期格式。
例子
[
{
"id": 4,
"name": "Charmander",
"age": 12.34,
"address": "Fire Street"
},
{
"id": 25,
"name": "Pikachu",
"age": 56.78,
"address": "Electric Street"
}
]
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 7/10 | JSON 具有简洁的语法,尽管在大多数情况下不如 YAML 和 TOML 简洁。 |
人类可读性 | 5/10 | JSON 是人类可读的。但由于不支持注释,因此它失去了一些分数。 |
语言支持 | 9/10 | JSON 有非常好的语言支持。 |
数据类型支持 | 6/10 | JSON 支持数组和映射(对象)结构。它支持许多不同的数据类型,包括字符串、数字、布尔值、null 等,但不支持日期。 |
速度 | 9/10 | JSON 是我评测过的序列化/反序列化速度最快的人类可读格式之一。有关更多信息,请参阅速度比较部分。 |
标准化 | 9/10 | JSON 有一个官方标准机构。请参阅https://www.json.org/。 |
协议缓冲区(Protobuf)
Protobuf 是 Google 开发的一种二进制序列化协议。由于它序列化为二进制,因此人类无法阅读(尽管在将文件视为 ASCII 文本时仍然可以挑选出字符串,请参见下面的示例)。
Protobuf 可以包含的数据类型定义明确,包括字符串、整数、浮点数和日期等常见类型。
例子
#^R^Charmander^Z^Fire Street%<A2>@
^A^R^Pikachu^Z^Electric Street%<F1>'OA
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 9/10 | Protobuf 是一种二进制格式,非常简洁。它还对整数等数据类型使用可变位编码,以减少数字较小时的字节数。如果它实施某种类型的编码方案,将获得 10/10。 |
人类可读性 | 1/10 | Protobuf 的设计目的并不是为了让人类可读。 |
语言支持 | 7/10 | Protobuf 支持 C、C++、C#、Dart、Go、Java、Objective-C、PHP、Python 和 Ruby。 |
数据类型支持 | 8/10 | Protobuf 允许您在 .proto 文件中定义数据结构。Protobuf 支持许多基本原始类型,这些原始类型可以组合成类,然后可以组合成其他类。 |
速度 | 9/10 | Protobuf 非常快,尤其是在 C++ 中(相对于其他序列化格式)。有关更多信息,请参阅速度比较部分。 |
标准化 | 9/10 | Protobuf 是由 Google 标准化的。 |
TOML
TOML(Tom 的显而易见的极简语言)是一种较新的(相对于本评论中的其他语言而言)人类可读的序列化格式。它与 YAML 非常相似,因为它面向配置文件,但它力求成为更简单的格式(YAML 可能变得非常复杂,这可以从 YAML 解析时间慢得多中看出)。
TOML 有适用于 Atom、Visual Studio、Visual Studio Code 和其他 IDE 的语法高亮器。
在表达对象数组(在 TOML 中,即表格数组)时,TOML 的语法有些冗长。以下示例显示了这一点,数组中的每个 pokemon 对象都以 分隔[[pokemon]]。
[[pokemon]]
id = 4
name = "Charmander"
address = "Fire Street"
age = 12.34
[[pokemon]]
id = 25
name = "Pikachu"
address = "Electric Street"
age = 56.78
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 7/10 | TOML 非常简洁,除了表格数组之外。 |
人类可读性 | 9/10 | TOML 的主要目标之一是让它变得非常容易理解。 |
语言支持 | 6/10 | TOML 是一种相对较新的序列化格式,并且没有像 JSON、CSV 或 XML 那样针对各种编程语言的库数量。 |
数据类型支持 | 9/10 | TOML 支持大多数常见数据类型,包括字符串、整数、浮点数和日期。TOML 不支持 YAML 那样的引用(可能是因为 TOML 力求简单)。 |
速度 | 6/10 | TOML 速度较慢,但比 YAML 快。有关更多信息,请参阅速度比较部分。 |
标准化 | 9/10 | TOML 已经标准化。 |
XML
XML 是一种人类可读的序列化协议。众所周知的类 XML 格式是 HTML,它用于确定网页的结构。
XML 的一个缺点是冗长。它的描述性结束标记要求您重新输入要关闭的元素的名称,这增加了 XML 数据的字节数。
它标准化程度很高,有大量工具可用于生成 XML 并使用架构对其进行验证。XML 的规范可在https://www.w3.org/TR/xml/找到。
XML 同时支持标准(DOM 样式)解析器和流式(SAX 样式)解析器。
例子
<people>
<person>
<id>4</id>
<name>Charmander</name>
<age>12.34</age>
<address>Fire St</address>
</person>
<person>
<id>25</id>
<name>Pikachu</name>
<age>56.78</age>
<address>Electric Street</address>
</person>
</people>
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 3/10 | XML 并不以简短和简洁而闻名。 |
人类可读性 | 5/10 | 人类可读,尽管您可能会迷失在眼前的所有标签之中。 |
语言支持 | 9/10 | 所有主流语言均受支持,通常带有内置库。 |
数据类型支持 | 9/10 | XML 非常灵活,因为每个元素可以具有属性和任意子元素。 |
速度 | 8/10 | XML 相当快,尽管解析速度通常比 JSON 慢。有关更多信息,请参阅速度比较基准测试部分。 |
标准化 | 10/10 | XML 是由 W3C 制定的标准化格式。W3C 还建议使用 XSD(XML 模式定义,也称为 WXS)作为 XML 的主要模式。 |
YAML(YAML 不是标记语言)
YAML 规范比 JSON 规范大得多。YAML 允许使用锚点 (`) 来表示关系数据(引用)。YAML 获得了一些额外的样式点,因为 YAML 主页甚至以 YAML 显示(https://yaml.org/)。
YAML 是 JSON 的严格超集,这意味着您可以使用 YAML 解析器解析 JSON(YAML 解析器可能需要更长时间,因此不要对大量 JSON 数据使用此技巧!)。
例子
- { id: 0, name: Charmander, age: 12.34, address: "Fire Street" }
- { id: 25, name: Pikachu, age: 56.78, address: "Electric Street" }
Review
财产 | 价值 | 评论 |
---|---|---|
简洁 | 9/10 | 值可以默认为字符串,这样您就可以省略引号。对于对象数组,它的语法比 TOML 更简洁(在 TOML 中,您必须在每个元素前加上[[array_name]] )。 |
人类可读性 | 7/10 | 基本 YAML 确实很容易阅读,但是 YAML 的复杂性可能会使读者在使用其高级功能时感到困惑。 |
语言支持 | 6/10 | YAML 非常流行,大多数流行语言都有它的库,但它并不像 CSV 或 JSON 那样普遍。 |
数据类型支持 | 10/10 | YAML 支持大多数常见数据类型,包括字符串、整数、浮点数和日期。YAML 甚至支持引用(关系数据)和外部数据! |
速度 | 2/10 | 在我测试的所有格式中,YAML 的序列化/反序列化运行时间是最慢的,无论是在 C++ 还是 Python 中(有关更多信息,请参阅速度比较部分)。 |
标准化 | 8/10 | YAML 标准化得很好,但很难找到额外的功能,比如模式验证器(我不得不使用jsonschema来验证 YAML)。 |
速度比较(基准测试)
以下库用于速度比较测试:
格式 | Python | C++ |
---|---|---|
CSV | csv(内置) | fast-cpp-csv-解析器(https://github.com/ben-strasser/fast-cpp-csv-parser) |
JSON | json (内置) | json(https://github.com/nlohmann/json) |
Protobuf | protobuf(https://github.com/protocolbuffers/protobuf) | protobuf(https://github.com/protocolbuffers/protobuf) |
TOML | toml(https://github.com/uiri/toml) | cpptoml(https://github.com/skystrife/cpptoml) |
XML | ElementTree (内置) | tinyxml2(https://github.com/leethomason/tinyxml2) |
YAML | PyYAML(https://pyyaml.org/) | yaml-cpp(https://github.com/jbeder/yaml-cpp) |
所有 Python 测试均使用 Python v3.7。所有 C++ 测试均使用 C++17/GCC 编译器。测试在虚拟机内运行的 Debian 机器上运行。此测试的目的是展示不同序列化格式之间的相对性能,这些性能不应受到虚拟机内运行的影响。
为了代表序列化数据的使用方式,所有写入测试都传递了相同的输入数据,无论是对象vector
(对于 C++ 测试)还是对象List
(对于 Python 测试)Person
。每个对象都Person
包含一个 ID(从 0 开始的整数)、一个名称(5 个 ASCII 字符的随机字符串)、一个地址(30 个 ASCII 字符的随机字符串)和一个年龄(浮点数)。每个测试都需要将数据序列化为所需的格式(使用上面提到的库),然后将序列化的数据写入磁盘。所有读取测试都执行相反的任务,读取数据文件、反序列化并创建vector
对象。List``Person
每个测试执行 3 次迭代,并选择其中最短的运行时间作为最具代表性的测试。较长的运行时间通常是由于操作系统执行了不应包含在这些运行时间中的无关任务。
格式 | C++ 反序列化 | C++ 序列化 | Python 反序列化 | Python 序列化 |
---|---|---|---|---|
源文件 | 0.030 | 0.022 | 0.027 | 0.034 |
json | 0.16 | 0.13 | 0.023 | 0.16 |
protobuf | 0.015 | 0.025 | 0.26 | 0.38 |
toml | 0.23 | 0.22 | 1.08 | 0.23 |
xml | 0.12 | 0.16 | 0.063 | 0.25 |
yaml | 0.48 | 0.55 | 6.84 | 3.84 |
ok
观察序列化如何响应数据大小的变化也很有趣。如果数据大小加倍,读取/写入所需的时间是加倍(线性响应),还是会有不同的表现(例如二次,
log(n), …
)?这称为序列化算法的复杂性。为了测试这一点,我将数组people从 10,000 增加到 100,000(增加了 10 倍)。结果如下……
格式 | C++ 反序列化 | C++ 序列化 | Python 反序列化 | Python 序列化 |
---|---|---|---|---|
源文件 | 0.26 | 0.18 | 0.22 | 0.38 |
json | 1.53 | 1.50 | 0.20 | 1.59 |
protobuf | 0.13 | 0.24 | 2.62 | 3.61 |
toml | 2.23 | 2.19 | 9.86 | 2.08 |
xml | 0.85 | 1.74 | 0.78 | 2.58 |
yaml | 4.96 | 5.70 | 69.67 | 36.87 |
执行这些测试的代码可以在https://github.com/gbmhunter/BlogAssets/tree/master/Programming/serialization-formats找到。
文件大小比较
当序列化大量数据时,另一个重要方面是格式的详细程度。为了比较不同格式的详细程度,我们可以向每种格式传递相同的数据,将数据转储到磁盘,然后比较文件大小。
格式 | 文件大小 (MiB,10k 条记录) | 文件大小 (MiB,100k 条记录) |
---|---|---|
源文件 | 0.41 | 4.2 |
json | 0.81 | 8.2 |
protobuf | 0.38 | 3.9 |
toml | 0.94 | 9.5 |
xml | 1.50 | 15 |
yaml | 0.80 | 8.1 |
正如预期的那样,文件大小随着存储的记录数量线性增长(10 倍数据量 = 10 倍文件大小)。
作为唯一一个被比较的二进制、非人类可读格式,protobuf 毫无疑问是最简洁的格式。紧随其后的是 CSV。由于 CSV 不支持不规则、非平面的数据结构,因此它只需要一个值分隔符(例如
,
)和行尾字符(例如\n
)。
未考虑的其他格式
- Apache Avro:Apache 的基于二进制的数据序列化标准。
- BSON . MongoDB 推广的一种基于 JSON 的二进制格式。
- CAN'N PROTO。“谷物化协议”……:-D。有关更多详细信息,请参阅其网站。
- JSON5 . JSON 的超集,允许使用多行字符串、注释、单引号字符串分隔符等。
- HOCON:一种在 Java 中流行的序列化格式。
- MessagePack。这看起来类似于 protobuf(使用二进制编码)。具有适用于多种语言的库。
- SDLang:一种类似 XML 的序列化格式(但没有尖括号),其库可在 .NET、Java、PHP、Ruby 和其他语言中使用。
- XDR(RFC4506):外部数据表示(XDR)是一种常用但专业的(即普通开发人员不会日常使用此协议)序列化协议。