序列化介紹

什麼是序列化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進行序列化。

相關文章
相關標籤/搜索