什么是序列化html
程序员在编写应用程序的时候每每须要将程序的某些数据存储在内存中,而后将其写入某个文件或是将它传输到网络中的另外一台计算机上以实现通信。这个将程序数据转化成能被存储并传输的格式的过程被称为“序列化”(Serialization),而它的逆过程则可被称为“反序列化”(Deserialization)。ios
简单来讲,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它根据流重构对象。这两个过程结合起来,能够轻松地存储和传输数据。例如,能够序列化一个对象,而后使用 HTTP 经过 Internet 在客户端和服务器之间传输该对象。c++
2 为何使用序列化2.1 哪些状况须要使用序列化2.1.1 以某种存储形式使自定义对象持久化程序员
经过序列化,能够将对象的状态保持在存储媒体中,在之后可以从新建立精确的副本。咱们常常须要将对象的字段值保存到磁盘中,并在之后检索此数据。尽管不使用序列化也能完成这项工做,但这种方法一般很繁琐并且容易出错,而且在须要跟踪对象的层次结构时,会变得愈来愈复杂。能够想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。数据库
2.1.2 将对象从一个地方传递到另外一个地方数组
一般来讲,对象仅在建立对象的应用程序域中有效。可是,序列化能够经过值将对象从一个应用程序域发送到另外一个应用程序域中。例如,序列化可用于在ASP.NET中保存会话状态并将对象复制到Windows窗体的剪贴板中。序列化最重要的目的之一就是在网络上传输对象。安全
2.2 序列化的优点服务器
在系统化的序列化方法出现以前,程序员若是想要将自定义的一个类的对象持久化地保存下来,并进行传输,能够采用如下这些方法:网络
l 由程序员本身实现保存对象数据的功能,针对每个对象编写代码,将其数据存储下来。数据结构
l 将对象强制转换为char*或者void*类型的数据,而后进行数据的传输。
下面将从通用性、便捷性、灵活性和可移植性的角度来比较序列化相对于上述两种方法的优点。
2.2.1 通用性
若是由程序员本身实现保存对象数据的功能,那么对于每个类的对象,程序员都要编写不一样的代码,工做量很大,通用性不高。而序列化提供了一套流程化的方法,对于每一种类,都是大致一致的流程,提升了代码的通用性。
若是将对象强制转换为char*或void*类型的数据进行传输,那么必须预先得知该对象的大小以提早分配数组的空间。可是,若是该对象中存在可变长的数据结构,就没法准确地得知对象数据的大小了,只能预先估计一下。若是估计小了,可能会形成空间溢出,程序崩溃的后果;若是估计大了,又会形成空间的浪费。可是,若是使用序列化的方法,就能很好地解决可变长数据结构的问题。
2.2.2 便捷性
若是由程序员本身实现保存对象数据的功能,那么对于类中不一样的数据结构,程序员都要编写相应的保存代码,简单的数据结构还好说,若是是具备多种层次的数据结构,代码的编写将愈来愈复杂,这样繁琐且容易出错。序列化提供了针对简单数据类型,以及字符串类型、STL容器、指针等种种数据类型的持久化的方法,只需简单地调用便可,具备很大的便捷性。
2.2.3 灵活性
序列化提供了若干种将对象数据持久化的格式,好比以简单文本格式保存、以XML格式保存、以SOAP格式保存、以二进制格式保存等等。还提供了多种保存持久化以后的对象的方式,好比保存到字符串、保存到文件等等,具备很大的灵活性。
2.2.4 可移植性
使用将对象强制转换为char*类型进行传输的方法,须要注意CPU字节序的问题。若是起始机器与目的机器的CPU字节序不一样,就会形成目的机器读到的数据没法恢复成原来对象的问题。虽然能够经过将本地字节序转化为网络字节序进行传输,传到目的机器以后再将网络字节序转为本地字节序的方法解决这个问题,可是这就增长了程序员考虑问题的复杂性。序列化屏蔽了字节序的差别,使得被持久化对象的传输更具备可移植性。
此外,使用序列化还能够很好地跨平台。
3 咱们的需求3.1 对基于OTT的数据库结构进行性能测试
在使用基于OTT的数据库结构的程序进行性能测试时,因为读入的PNR数据是XML格式的文档,因此,读入XML文件到内存,将其转为DOM树,继而将DOM树中的数据转化为OTT数据库所须要的对象结构,须要耗费大量的时间。若是把这部分时间算在程序的性能时间中,将致使测试出来的性能存在较大的偏差。所以,最好的方式是,事先将XML格式的PNR数据转化为程序可用的对象,在程序运行时直接读入对象便可。这样能够将解析XML格式的PNR数据的时间与程序运行的时间分离开,从而保证了性能测试的准确性。而将PNR数据转为程序可用的对象保存下来,就是一个对象序列化的过程;程序读入保存对象的文件并将其恢复为原来的对象,这就是一个对象反序列化的过程。
3.2 只能使用某种特定类型进行数据传输的状况
在某些状况下,因为种种限制的约束,使得数据的传输只能使用某种特定的类型。好比,使用Tuxedo时,从客户端向服务端传数据只可使用char*类型;好比,在使用共享内存传递数据时,只能采用连续的数组形式。在这些状况下,若是传输的数据是一个自定义类的对象的话,就会遇到挑战。一种作法是直接将该对象强制转化为所限定的类型,传到目的地以后再由限定的类型强制转为原来的类型。这种作法在性能上应该最快,可是使用这种方法必须得明确地知道所传出数据的长度,因此发送变长数据并不方便。此外,它还存在跨平台的兼容性问题。另外一种作法就是利用对象序列化的方法,将对象保存为字节流,向目的地传输,在目的地再反序列化为自定义类的对象。这种方法相对比较通用,安全和规范,可是性能上可能不如前一种方法。
4 使用C++将对象进行序列化的几种方法
使用C++进行对象序列化的方法能够有如下三种:基于Boost库的方法;基于.Net Framework的方法;以及基于MFC的方法。本章将就三种方法的实现机制、实现步骤,以及注意事项进行说明。
因为咱们的开发环境在Windows下,部署环境在Unix下,所以咱们的开发须要使用两个平台均可以兼容的技术。通过验证,基于.Net和基于MFC的方法仅适用于Windows的环境,而Boost库在Windows和Unix下都有相应的版本,所以在项目中应优先考虑使用Boost库进行对象的序列化。尽管如此,本文中仍然列出使用.Net和MFC进行序列化的方法,以供参考。三种方法相应的代码实现的例子将附在文章以后。
4.1 使用Boost库4.1.1 实现机制
这里,咱们用术语序列化(serialization)来表示将一组原始的C++数据结构表示为字节流达到可逆析构的目的。这样的系统能够用来在另外一个程序环境中从新创建原来的数据结构。所以,它也能够做为对象持久性(object persistence),远程参数传递(remote parameter passing),或者其余特性的实现基础。在咱们的系统中,将使用术语档案(archive)表示一个具体的字节流。档案能够是二进制文件,文本文件,XML文件,或者其余用户定义的类型。
Boost序列化库的目标是:
l 代码的可移植性–只依靠ANSI C++的特性。
l 代码的经济性–挖掘各类C++的特性如RTTI、模板、和多继承等等使用户容易使用而且代码短小。
l 类版本的独立性。–当一个类的定义改变时,老版本的类的档案仍然能够被导入新版本的类中。
l 指针的深度存储和恢复。–保存或恢复指针的同时保存或恢复指针指向的数据。
l 正确的处理多个指针指向相同对象时的问题。
l 对STL和其余经常使用模板类的序列化的直接支持。
l 数据的可移植性–在一个平台上创建的字节流在另外一个平台上也应该是正确的。
l 序列化和档案格式的正交性–能够在不改变类的序列化部分时应用任何格式的文件做为档案。
l 支持非侵入(Non-intrusive)式的实现。类不须要从某个特定的类派生或者实现特定的成员函数。这对于咱们不能或不肯意修改类的定义的状况时是至关必要的。
l 档案的接口应该足够简单使创建新类型的档案的工做变得轻松。
l 档案应该支持XML格式。
Boost中,与序列化有关的两个库是Archive库和Serialization库。
4.1.2 实现步骤
首先,为被序列化的类实现一个对应的serialize(Archive & ar, const unsigned int version)方法;
其次,构造boost::archive::text_oarchive类或其余archive输出类的对象,并将其关联到一个输出流,利用<<运算符将被序列化的对象输出到某个文档中;
最后,构造boost::archive::text_iarchive类或其余archive输入类的对象,并将其关联到一个输入流,读入数据,利用>>运算符会付出被序列化的对象。
4.1.3 注意事项
使用这种方法须要注意的是:
l Boost从1.32版本以后才提供对序列化的支持,因此必定要用版本在1.32以后的;
l Boost中的Serialization库须要编译以后获得库文件才能使用,并加入项目的附加依赖项中才可以使用;
l 根据须要包含boost/serialization和boost/archive下的一些头文件。
4.2 使用.NET4.2.1 实现机制
.NET的运行时环境用来支持用户定义类型的流化的机制。它在此过程当中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,而后再把字节流写入数据流。在随后对对象进行反序列化时,将建立出与原对象彻底相同的副本。
.Net框架对序列化机制具备很是好的支持,它提供了两个名字空间(namespace):System.Runtime.Serialization和System.Runtime.Serialization.Formatters以完成序列化机制的大部分功能。
序列化机制的实现是依靠格式器(Formatter)而完成的,它是一个从System.Runtime.Serialization.IFormatter继承下来的类的对象。格式器完成了将程序数据转化到能被存储并传输的格式的工做,同时也完成了将数据转化回来的工做。.Net框架为程序员提供了两种类型的格式器,一种一般是应用于桌面类型的应用程序的,它一个是System.Runtime.Serialization.Formatters.Binary.BinaryFormatter类的对象,而另外一种则更主要的应用于.Net Remoting和XML Web服务等领域的,它一个是System.Runtime.Serialization.Formatters.Soap.SoapFormatter类的对象。从它们的名称来看,不妨将它们分别称为二进制格式器和XML格式器。它们对应于.Net提供的两种序列化技术:
二进制序列化保持类型保真度,这对于在应用程序的不一样调用之间保留对象的状态颇有用。例如,经过将对象序列化到剪贴板,可在不一样的应用程序之间共享对象,能够将对象序列化到流、磁盘、内存和网络等等。它的优势在于能够将全部的对象成员都保存下来,而且性能优于XML序列化。
XML 序列化仅序列化公共属性和字段,且不保持类型保真度。当您要提供或使用数据而不限制使用该数据的应用程序时,这一点是颇有用的。因为 XML 是一个开放式标准,所以,对于经过 Web 共享数据而言,这是一个很好的选择。SOAP 一样是一个开放式标准,这使它也成为一个颇具吸引力的选择。它的优势在于互操做性好,可读性强。
4.2.2 实现步骤
使用.Net下的二进制序列化方法进行对象序列化的步骤以下:
首先,要使用 Serializable 属性对对象的类进行标记;
其次,利用BinaryFormatter的Serialize方法将对象写入到一个文件流中;
最后,利用BinaryFormatter的DeSerialize方法读取文件流,恢复对象。
4.2.3 注意事项
使用这种方法须要注意的是:
l 须要使用System::Runtime::Serialization::Formatters::Binary命名空间和 System::Runtime::Serialization命名空间;
l 被序列化的类在声明时必须标识[Serializable]属性;
l 所涉及的类必须是托管类,即类的声明前须要有ref关键字,用gcnew关键字表示在托管堆上分配内存,指针符号用^来标识等。
4.3 使用MFC4.3.1 实现机制
对象的序列化归根结底是将对象的数据写入载体,再从新读取为对象的过程。MFC中对数据的读写创造了十分好的支持,这使得咱们能够十分方便的利用MFC的数据读写类来实现对象序列化的须要。
MFC 为数据读写设计了三个基本的类——CFile(CFile类)、CStdioFile(标准I/O文件类)、CArchive(CArchive类)。其中标准CStdioFile类提供至关于C的流式文件的功能,能够用文本或者二进制方式打开,能够被缓冲。CFile类提供了非缓冲的二进制输入输出文件,它既能够与CArchive类结合实现VisualC++设计中经常使用的文件序列化,也能够由设计者本身订制存储方案,实现数据的读写操做(此方法的兼容问题须要解决,保密性强)。CArchive类是VisualC++程序设计中最经常使用的文件处理的方法,CArchive类不只能够实现简单数据结构的读写操做,还能够经过对CObiect类的派生实现对复杂数据结构的读写操做,所以,利用CArchive类,能够轻松实现任意数据结构的序列化。
4.3.2 实现步骤
实现序列化的的类须要知足一系列条件:
1. 该类须要从CObject类派生(能够是间接派生);
2. 在类中中进行DECLARE_SERIAL宏定义;
3. 类存在有缺省的构造函数;
4. 类中实现了Serialize(CArchive&)函数,而且在其中调用基类的序列化函数;
5. 使用IMPLEMENT_SERIAL宏指明类名及版本号。
知足了这些条件以后,就能够进行序列化与反序列化了。
序列化时,首先,实例化一个CArchive类的对象,将其与输出文件相关联;其次,利用CArchive类的<<运算符重载将须要序列化的对象保存在文件中。
反序列化时,将CArchive类的对象与保存对象的文件相关联;而后新建一个须要反序列化的对象,利用CArchive类的>>运算符重载将文件里的内容恢复到须要反序列化的对象中。
4.3.3 注意事项
使用这种方法须要注意的是:
l 须要包含afx.h头文件;
l 它不支持string类型的序列化,可是支持CString类型的序列化;
l 须要将项目属性中的MFC属性配置为“在共享DLL中使用MFC”或“在静态库中使用MFC”,不然编译时会报错。
5 使用Boost库进行对象序列化的关键技术5.1 基础
一、基本类型的存档和读取
对基本类型. 直接使用如下语句就能够完成存档或读取:
l 用 ar << data或ar & data; 写入存档
l 用 ar >> data或ar & data; 从存档取出
二、自定义类型的存档和读取
对自定义类型. 则会调用 serialize() 函数,serialize 函数用来“存储/装载”其数据成员。这个处理采用递归的方式,直到全部包含在类中的数据“被存储/被装载”。
l 侵入式: t.serialize(ar, version)
l 非侵入式: serialize(ar, t, version)
三、所需包含的头文件:
l 以简单文本格式实现存档:text_oarchive和text_iarchive
l 宽字符的文本格式存档 :text_woarchive text_wiarchive
l xml存档:xml_oarchive xml_iarchive
l 使用宽字符的xml文档(用于utf-8)输出:xml_woarchive xml_wiarchive
l 二进制的存档 (注意 二进制存档是不可移植的):binary_oarchive binary_iarchive
5.2 侵入式和非侵入式
对于被序列化的类,有两种实现其对应的serialize方法的方式,一种是侵入式,即把serialize方法做为被序列化类的一个成员方法来实现;另外一种是非侵入式,即将serialize方法放在另外一个名字空间下,做为被序列化类的一个友元方法来实现。在不可修改被序列化的类的代码的状况下,应该采用非侵入式的方式。
侵入式的例子:
class MyPoint
{
int mX;
int mY;
private:
friend class boost::serialization::access; //侵入式版本的要加这个.
//存入和读取都使用下边的 serialize() 函数.
//其中的 Archive 是一个输入或输出的文档. 当输入的时候 & 为 >> . 当输出的时候 & 为 <<.
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & mX; //序列化数据成员
ar & mY;
}
public:
MyPoint() {}
MyPoint(int x, int y) : mX(x), mY(y) {}
};
非侵入式的例子:
class MyPoint
{
private:
// 注意关键字”friend”和多了一个类引用做参数
template<class Archive>
friend void serialize(Archive& ar, MyPoint&, unsigned int const);
int mX;
int mY;
public:
MyPoint() {}
MyPoint(int x, int y) : mX(x), mY(y) {}
};
//非侵入式
namespace boost { //实现放在这个名字空间下
namespace serialization {
template<class Archive>
void serialize(Archive & ar, MyPoint& p, const usigned int version)
{
ar & p.mX & p.mY; //能够连着 &
}
}
} //namespace 结束
5.3 派生类的序列化
对派生类进行序列化须要有一个前提,即它的父类必须也实现了serialize方法,也能够序列化。若是在派生类的父类没有实现serialize方法,仅对派生类进行序列化,将不能保存派生类从父类继承下来的数据信息,而仅能保存属于派生类自身的数据信息。
对派生类进行序列化的步骤是:
一、包含boost/serialization/base_object.hpp头文件;
二、在serialize模版方法中,使用ar & boost::serialization::base_object<父类>(*this)这样的语法来保存父类的数据,不能直接调用父类的serialize函数。
一个例子以下:
#include <boost/serialization/base_object.hpp> //必定要包含此头文件
class B:A
{
friend class boost::serialization::access;
char c;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<A>(*this); //注意这里
ar & c;
}
public:
…
};
5.4 数组的序列化
对于数组进行序列化,就是保存数组中的每个数据成员,所以至关于对数组中的每个数据成员作序列化。能够用如下形式:
for(int i = 0; i < sizeof(array); i++)
{
ar & array[i];
}
可是事实上,Boost的序列化库能检测出被序列化的对象是一个数组,将产生上述等价的代码,例子以下:
class bus_route
{
friend class boost::serialization::access;
bus_stop stops[10];
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & stops;
}
public:
bus_route(){}
};
5.5 指针的序列化
序列化整个对象是要求在另外一个地方和时间从新构造原始数据结构。在使用指针的状况下,为了达到从新构造原始数据结构的目的,仅仅存储指针的值是不够的,指针指向的对象也必须被存储。当成员最后被装载,一个新的对象被建立,指向新的对象的新的指针被装载到类的成员中。
全部这一切由Boost的序列化库自动完成,程序员只需直接序列化指针便可。(说是这么说,使用要慎重,由于例子并无调通。)一个例子以下:
class bus_route{ friend class boost::serialization::access; bus_stop * stops[10]; template<class Archive> void serialize(Archive & ar, const unsigned int version) { int i; for(i = 0; i < 10; ++i) ar & stops[i]; }public: bus_route(){}};5.6 对STL容器的序列化
对于STL容器,好比vector或list,须要在头文件中包含<boost/serialization/vector.hpp>或<boost/serialization/list.hpp>等,而后就能够直接进行序列化了。一个例子以下:
#include <boost/serialization/list.hpp>class bus_route{ friend class boost::serialization::access; std::list<bus_stop *> stops; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & stops; }public: bus_route(){}};5.7 被序列化的类的成员是其余类的对象的状况
若是被序列化的类有成员是其余类的对象,那么,只有在其对象成员的类也实现了serialize方法并能被序列化的状况下,该类才能被序列化。
好比前几个例子中,类bus_route中有成员是bus_stop类的对象。那么只有bus_stop类实现了serialize方法后,bus_route类才能被序列化。
5.8 输出
Boost的序列化库能够以三种格式进行输出,分别是:简单文本格式、XML格式,以及二进制格式。其中每种格式又能够输出到c++的ostream流中,好比,ostringstream(字符串输出流),ofstream(文件输出流)。下例是一个以简单文本格式输出到字符串流中的例子。
//序列化,输出到字符串
std::ostringstream ossOut(ostringstream::out); //把对象写到字符串输出流中
boost::archive::text_oarchive oa(ossOut);
TestClass objTestClass;
oa << objTestClass;
string strTrans = ossOut.str();
……
//反序列化,从字符串输入
istringstream ossIn(strTrans); //从字符串输入流中读入数据
boost::archive::text_iarchive ia(ossIn);
TestClass newObjTestClass;
ia >> newObjTestClass;
6 结论
一、 在基于OTT结构的数据库结构的性能测试中,针对数据库中的每个表,定义了一个相应的类,咱们的目标是将这些类的对象进行序列化。可是,在试图序列化的过程当中遇到一个问题,即:全部的OTT表的类都继承自一个由Oracle库文件定义的类oracle::occi::PObject。而派生类的序列化要求其父类也必须实现序列化接口,不然就会派生类继承的父类的成员就会在序列化时丢失(见5.3节)。这就要求修改库文件,是PObject也实现序列化接口。但是贸然地修改库文件可能会致使连锁反应,引发其余引用库文件的程序出错,此外,还有知识产权的问题。因此,使用Boost序列化库来对OTT表的类进行序列化的路可能走不通。应考虑其余方法。
二、 在使用共享内存传递对象数据时,能够将对象数据以简单文本格式进行序列化,再用ostringstream流输出到字符串中,进行传递,彻底可行。
7 附录7.1 资源
一、 Boost中Serialization库的文档:http://www.boost.org/doc/libs/1_37_0/libs/serialization/doc/index.html;
二、 Boost序列化库教程:http://dozb.bokee.com/1692310.html#derivedclasses;
三、 Learning boost 1 Serialization:http://blog.csdn.net/freerock/archive/2007/08/17/1747928.aspx
四、 C++中使用boost::serialization库――应用篇:http://www.cnblogs.com/mslk/archive/2005/11/25/284491.html;
五、 C++ Reference: IOstream Library: ostream:http://www.cplusplus.com/reference/iostream/ostream/;
7.2 程序示例对照表
l CplusSerializeBoost:使用Boost的序列化库进行序列化;
l CplusSerializeDotNet:使用.Net进行序列化;
l CplusSerializeMFC:使用MFC进行序列化。