Cereal library--序列化类型

来源:互联网 发布:java 一个对象的大小 编辑:程序博客网 时间:2024/05/17 08:08

本文翻译自:
http://uscilab.github.io/cereal/serialization_archives.html

Cereal支持二进制、XML和JSON三种格式。

TLDR版本

Cereal支持二进制、XML和JSON三种格式的读写操作,在使用时包含对应的头文件,例如’cereal/archives/xxxx.hpp’。如果要使用多态,则需要在多态声明之前包含对应的头文件。

基本知识

存储格式决定了数据是如何被输出和读取。不过大多数情况下,我们并不需要知道某种存储格式的内部工作原理,因为使用时Cereal已经做了很好的封装。但是为了更加个性化,Cereal也只是为不同的序列化格式设置对应的序列化函数。

Cereal的读写操作是基于C++的std::ostream和std::istream。这意味着,操作对象可以是文件、内存流,甚至标准的输入输出。

值得注意的是,Cereal的工作方式是RAII规范,即只有在存储类被销毁时,才能完全保证完全输出。换句话说,如果存储类没有被销毁,甚至可能存在没有输出任何信息。因此,合理使用Cereal的方式是使用大括号来规定存储类的生存范围,保证存储类在合适的时候自动销毁。

#include <cereal/archives/xml.hpp>#include <fstream>{  std::ofstream file( "out.xml" );  cereal::XMLOutputArchive archive( file );   archive( some_data, more_data, data_galore );} // 当程序运行到此处时,Cereal的存储类就会自动销毁,从而完成序列化操作

Cereal在存储设计时,并没有考虑长期的兼容性,而是将这个兼容性工作移交给开发者自行处理。一种比较合理的方式时,尽量保证在读写时都使用同一个版本的Cereal。
好消息时,Cereal和Boost一样,也支持在序列化内容中添加版本信息,相关信息请参考“Cereal序列化函数”。

高级模式:多态
关于多态模式下序列化的详细信息,可以参考“Cereal高级模式之多态”。此处简单说一下,即如果序列化一个多态派生类,必须在声明之前包含Cereal头文件。

二进制序列化

二进制序列化需要包含cereal/archives/binary.hpp。二进制类型是一种bit级压缩数据格式,因此无法直接供人阅读。二进制序列化是Cereal中最快的一种序列化方式,在序列化时只序列化变量的值,而不是“变量名-值”这种方式。
通常情况下,二进制序列化并不保证字节顺序。如果你需要保证在不同的平台上都能正常工作,则需要包含cereal/archives/portable_binary.hpp,从而保证字节顺序的一致性,但是这样会稍微降低性能。
当使用二进制序列化和文件流(std::fstream)时,记住使用二进制flag(std::ios::binary)构造流,这样能够保证流使用ASCII读写数据。

XML序列化

XML序列化需要包含cereal/archives/xml.hpp。XML序列化是一种可供人阅读的格式,并且在序列化对象数据量较大时最好不要使用该格式。与二进制序列化逐步输出内容不一样的是,XML序列化会在内存中维护一个树形结构,并且仅在其析构时输出内容。
XML序列化使用了“变量名-值”模式,对于人来说有非常好的可阅读性。如果不设置“变量名-值”,XML序列化会自动生成一个数字的变量名。XML序列化在序列化时不需要元数据来设置变量的大小,因为在反序列化时会自动查询数据大小。这意味着在反序列化之前,可用手动修改XML文件来达到需要的目的。

下文中给出一个XML序列化的示例:
代码

#include <iostream>#include <cereal/archives/xml.hpp>#include <cereal/types/vector.hpp>int main(){  cereal::XMLOutputArchive archive( std::cout );  bool arr[] = {true, false};  std::vector<int> vec = {1, 2, 3, 4, 5};  archive( CEREAL_NVP(vec),           arr );}

结果

<?xml version="1.0"?><cereal>  <vec size="dynamic"> <!-- 注意此处并没有设置list的大小,Cereal自动标记为动态模式 -->    <value0>1</value0> <!-- 如果没有使用NVP设置,变量名会自动设置 -->    <value1>2</value1>    <value2>3</value2>    <value3>4</value3>    <value4>5</value4>    <!-- 此处可以插入新的数据,反序列化时也可以正常读取 -->  </vec>  <value0> <!-- 注意,由于数组的大小是固定的,所以此处没有大小数据 -->    <value0>true</value0>    <value1>false</value1>  </value0></cereal>

如果你一定要手动编辑XML文件,需要注意新添加的数据是合法的,否则在反序列化时会报错,并且只能在dynamic标签下添加数据。

XML可以通过使用属性标签的方式输出完整的类型信息,并且还可以控制浮点数的输出精度。进一步说明,如果你要保证浮点数的精度,需要手工设置浮点数的输出精度(float是10,double是20,long double是40)。默认情况下,Cereal会使用最大可能的保证double的输出精度。

XML序列化依赖了RapidXML。

乱序反序列化
默认情况下,反序列化时都是按照存储的顺序进行数据读取,但是在XML(也包括JSON)都支持乱序反序列化。乱序的依据是之前提到的“变量名-值”,可以根据变量名来进行序列化。

当使用NVP宏进行反序列化时,Cereal会检查下一个按顺序待序列化内容的命名是否符合当前变量名。如果变量名不相同,Cereal会在剩下的节点中搜索该变量名。如果没找到变量名,就会抛出一个异常。反之如果找到了,Cereal即将该值反序列化,并从找到的节点开始,继续后续处理。

考虑如下的XML文件:

<?xml version="1.0"?><cereal>  <var1>4</var1>  <value0>32</value0>  <value1>64</value1>  <myData>    <value0>true</value0>    <another>3.24</another>  </myData>  <value2>128</value2></cereal>

下文中是如何乱序反序列化的代码:

#include <cereal/archives/xml.hpp>#include <iostream>struct MyData{  bool   b;  double d;  template <class Archive>  void serialize( Archive & ar )  {    ar( b, d );  }};int main(){  int i1, i2, i3, i4;  MyData md;  {    std::ifstream is("data.xml");    cereal::XMLInputArchive ar(is);    // 提前反序列化myData    ar( cereal::make_nvp("myData", md));    //从找到的节点往后反序列化    ar( i4 );                               ar( cereal::make_nvp("var1", i1) );     ar( i2, i3 );                                                                          // 再次读取到myData这个节点,由于变量名不存在抛出异常    ar( cereal::make_nvp("doesNotExist", i1) );   }  return 0;}

注意:Cereal的默认反序列化顺序是按顺序操作。如果你使用了NVP宏进行乱序反序列化,那么Cereal将会从找到的节点开始继续往后进行反序列化!

二进制输出
XML也支持通过将数据编码成string,从而二进制的输入输出。下文中的代码显示了这种功能。

int main(){  cereal::XMLOutputArchive archive( std::cout );  int arr[] = {-1, 95, 3};  archive.saveBinaryValue( arr, sizeof(int) * 3, "some_optional_name" );}

输出如下:

<?xml version="1.0"?><cereal>  <some_optional_name>/////18AAAADAAAA</some_optional_name></cereal>

相应使用loadBinaryValue进行读取。

JSON序列化

使用JSON进行序列化时只需要包含cereal/archives/json.hpp即可。JSON格式也是一种可供人阅读的格式。Cereal官方建议不要使用JSON格式序列化重要的数据。JSON和XML非常相似,也可以使用“变量名-值”的方式进行序列化,默认是随机生成变量名。下文中是一个Cereal生成JSON文档的代码,和XML非常类似。

#include <iostream>#include <cereal/archives/json.hpp>#include <cereal/types/vector.hpp>int main(){  cereal::JSONOutputArchive archive( std::cout );  bool arr[] = {true, false};  std::vector<int> vec = {1, 2, 3, 4, 5};  archive( CEREAL_NVP(vec),           arr );}

输入结果如下:

{    "vec": [        1,        2,        3,        4,        5    ],    "value0": {        "value0": true,        "value1": false    }}

可以发现,动态容器(例如std::vector)被序列化成了JSON数组,固定容器(例如std::array)被序列化成了JSON对象。这一点非常重要!当需要手动编写的JSON文件时,一定要注意上述规则。

在JSON中,除了long long,unsigned long long,long double被序列化成string,其他所有的数据都是序列化成数字。JSON中数字没有双引号包含,而string则会使用双引号包含。

JSON序列化依赖了RapidJson。

乱序反序列化
JSON序列化也支持乱序反序列化,具体操作和XML的类似,不再 赘述。

二进制输出
此处也和XML类似,所使用的函数也是saveBinaryValue和loadBinaryValue。

更多序列化类型

对于Cereal来说,添加其他类型的序列化类型是非常简单的。但是此处我就不翻译了,有点复杂~~,如果想了解更多信息,请参考原文。

原创粉丝点击