今天說(shuō)說(shuō)最常用的(protobuf和boost serialization)兩種C++序列化方案的使用心得
1. 什么是序列化?
2. 為什么要序列化?好處在哪里?
3. C++對(duì)象序列化的四種方法
4. 最常用的兩種序列化方案使用心得
1. 什么是序列化?
程序員在編寫(xiě)應(yīng)用程序的時(shí)候往往需要將程序的某些數(shù)據(jù)存儲(chǔ)在內(nèi)存中,然后將其寫(xiě)入某個(gè)文件或是將它傳輸?shù)骄W(wǎng)絡(luò)中的另一臺(tái)計(jì)算機(jī)上以實(shí)現(xiàn)通訊。這個(gè)將 程序數(shù)據(jù)轉(zhuǎn)化成能被存儲(chǔ)并傳輸?shù)母袷降倪^(guò)程被稱為“序列化”(Serialization),而它的逆過(guò)程則可被稱為“反序列化” (Deserialization)。
簡(jiǎn)單來(lái)說(shuō),序列化就是將對(duì)象實(shí)例的狀態(tài)轉(zhuǎn)換為可保持或傳輸?shù)母袷降倪^(guò)程。與序列化相對(duì)的是反序列化,它根據(jù)流重構(gòu)對(duì)象。這兩個(gè)過(guò)程結(jié)合起來(lái),可以輕 松地存儲(chǔ)和傳輸數(shù)據(jù)。例如,可以序列化一個(gè)對(duì)象,然后使用 HTTP 通過(guò) Internet 在客戶端和服務(wù)器之間傳輸該對(duì)象。
總結(jié)
序列化:將對(duì)象變成字節(jié)流的形式傳出去。
反序列化:從字節(jié)流恢復(fù)成原來(lái)的對(duì)象。
2. 為什么要序列化?好處在哪里?
簡(jiǎn)單來(lái)說(shuō),對(duì)象序列化通常用于兩個(gè)目的:
(1) 將對(duì)象存儲(chǔ)于硬盤(pán)上 ,便于以后反序列化使用
(2)在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列
對(duì)象序列化的好處在哪里?網(wǎng)絡(luò)傳輸方面的便捷性、靈活性就不說(shuō)了,這里舉個(gè)我們經(jīng)常可能發(fā)生的需求:你 有一個(gè)數(shù)據(jù)結(jié)構(gòu),里面存儲(chǔ)的數(shù)據(jù)是經(jīng)過(guò)很多其它數(shù)據(jù)通過(guò)非常復(fù)雜的算法生成的,由于數(shù)據(jù)量很大,算法又復(fù)雜,因此生成該數(shù)據(jù)結(jié)構(gòu)所用數(shù)據(jù)的時(shí)間可能要很久 (也許幾個(gè)小時(shí),甚至幾天),生成該數(shù)據(jù)結(jié)構(gòu)后又要用作其它的計(jì)算,那么你在調(diào)試階段,每次運(yùn)行個(gè)程序,就光生成數(shù)據(jù)結(jié)構(gòu)就要花上這么長(zhǎng)的時(shí)間,無(wú)疑代價(jià) 是非常大的。如果你確定生成數(shù)據(jù)結(jié)構(gòu)的算法不會(huì)變或不常變,那么就可以通過(guò)序列化技術(shù)生成數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)存儲(chǔ)到磁盤(pán)上,下次重新運(yùn)行程序時(shí)只需要從磁盤(pán)上讀 取該對(duì)象數(shù)據(jù)即可,所花費(fèi)時(shí)間也就讀一個(gè)文件的時(shí)間,可想而知是多么的快,節(jié)省了我們的開(kāi)發(fā)時(shí)間。
3. C++對(duì)象序列化的四種方法
將C++對(duì)象進(jìn)行序列化的方法一般有四種,下面分別介紹:
3.1 google Protocol Buffers(protobuf)
Google Protocol Buffers (GPB)是Google內(nèi)部使用的數(shù)據(jù)編碼方式,旨在用來(lái)代替XML進(jìn)行數(shù)據(jù)交換。可用于數(shù)據(jù)序列化與反序列化。主要特性有:
- 高效
- 語(yǔ)言中立(Cpp, JAVA, Python)
- 可擴(kuò)展
- 官方文檔
3.2 Boost.Serialization
Boost.Serialization可以創(chuàng)建或重建程序中的等效結(jié)構(gòu),并保存為二進(jìn)制數(shù)據(jù)、文本數(shù)據(jù)、XML或者有用戶自定義的其他文件。該庫(kù)具有以下吸引人的特性:
- 代碼可移植(實(shí)現(xiàn)僅依賴于ANSI C++)。
- 深度指針保存與恢復(fù)。
- 可以序列化STL容器和其他常用模版庫(kù)。
- 數(shù)據(jù)可移植。
- 非入侵性。
3.3 MFC Serialization
windows平臺(tái)下可使用MFC中的序列化方法。MFC 對(duì) CObject 類中的序列化提供內(nèi)置支持。因此,所有從 CObject 派生的類都可利用 CObject 的序列化協(xié)議。
MSDN中的介紹
3.4 .Net Framework
.NET的運(yùn)行時(shí)環(huán)境用來(lái)支持用戶定義類型的流化的機(jī)制。它在此過(guò)程中,先將對(duì)象的公共字段和私有字段以及類的名稱(包括類所在的程序集)轉(zhuǎn)換為字節(jié)流,然后再把字節(jié)流寫(xiě)入數(shù)據(jù)流。在隨后對(duì)對(duì)象進(jìn)行反序列化時(shí),將創(chuàng)建出與原對(duì)象完全相同的副本。
3.5 簡(jiǎn)單總結(jié)
這幾種序列化方案各有優(yōu)缺點(diǎn),各有自己的適用場(chǎng)景。其中MFC和.Net框架的方法適用范圍很窄,只適用于Windows下,且.Net框架方法還需要.Net的運(yùn)行環(huán)境。參考文獻(xiàn)1從序列化時(shí)間、反序列化時(shí)間和產(chǎn)生數(shù)據(jù)文件大小這幾個(gè)方面比較了前三種序列化方案,得出結(jié)論如下(僅供參考):
Google Protocol Buffers效率較高,但是數(shù)據(jù)對(duì)象必須預(yù)先定義,并使用protoc編譯,適合要求效率,允許自定義類型的內(nèi)部場(chǎng)合使用。
Boost.Serialization 使用靈活簡(jiǎn)單,而且支持標(biāo)準(zhǔn)C++容器。
相比而言,MFC的效率較低,但是結(jié)合MSVS平臺(tái)使用最為方便。
為了考慮平臺(tái)的移植性、適用性和高效性,推薦大家使用Google的protobuf和Boost的序列化方案,下面介紹我使用這兩種方案的心得及注意事項(xiàng)。
4. 最常用的兩種序列化方案使用心得
關(guān)于這兩種方案的具體使用和示例沒(méi)什么好寫(xiě)的,因?yàn)閮?yōu)秀的參考資料很多,請(qǐng)看后面給出的相關(guān)參考資料,這里只給出我使用時(shí)的一些心得,方便大家在選擇序列化方案時(shí)有個(gè)正確的參考,避免選擇錯(cuò)誤,浪費(fèi)時(shí)間。
4.1 Google Protocol Buffers
protobuf相對(duì)而言效率應(yīng)該是最高的,不管是安裝效率還是使用效率,protobuf都很高效,而且protobuf不僅用于C++序列化,還可用于Java和Python的序列化,使用范圍很廣。但在使用過(guò)程中要注意兩個(gè)問(wèn)題:
(1)protobuf支持的數(shù)據(jù)類型不是很豐富
protobuf屬于輕量級(jí)的,因此不能支持太多的數(shù)據(jù)類型,下面是protobuf支持的基本類型列表,一般都能滿足需求,不過(guò)在選擇方案之前,還是先看看是否都能支持,以免前功盡棄。同樣該表也值得收藏,作為我們?cè)诙x類型時(shí)做參考。


(2)protobuf不支持二維數(shù)組(指針),不支持STL容器序列化
這個(gè)缺陷挺大,因?yàn)樯詮?fù)雜點(diǎn)的數(shù)據(jù)結(jié)構(gòu)或類結(jié)構(gòu)里出現(xiàn)二維數(shù)組、二維指針和STL容器(set、list、map等)很頻繁,但因?yàn)?protobuf簡(jiǎn)單的實(shí)現(xiàn)機(jī)制,只支持一維數(shù)組和指針(用repeated修飾符修飾),不能使用repeated repeated來(lái)支持二維數(shù)組, 也不支持STL,因此在選擇該方案之前,一定 要確保你的數(shù)據(jù)結(jié)構(gòu)里沒(méi)有這些不支持的類型。
(3)protobuf嵌套后會(huì)改變類名稱
protobuf支持類的嵌套,即在一個(gè)自定義類型中可以定義另一個(gè)自定義類型,但注意嵌套的自定義類型在經(jīng)過(guò)protobuf處理后生成的類名稱并不是你定義的類名稱,而是加上了外層的類名稱作為前綴,下面舉一個(gè)簡(jiǎn)單的例子:
message DFA {
required int32 _size =1;
message accept_pair {
required boolis_accept_state =1;
required boolis_strict_end =2;
optional stringApp_name =3;
}
repeated accept_pair accept_states =2;
}
那么嵌套中的accept_pair 生成后的類不是accept_pair 而是DFA_accept_pair 。如果不想改類名稱,將accept_pair 拿到外面與DFA平行定義即可。
4.2 Boost.Serialization
Boost庫(kù)是個(gè)很龐大的庫(kù),功能非常豐富,序列化只是其中的一個(gè)小分支,但為了使用Boost的序列化方案,你需要安裝整個(gè)Boost庫(kù),所花費(fèi)的磁盤(pán)空間和時(shí)間都很多,同樣支持的序列化功能也很強(qiáng)大,既支持二維數(shù)組(指針),也支持STL容器,更不需要我們用某種特殊的格式重新定義我們的類結(jié)構(gòu),其非侵入的性質(zhì)使得我們無(wú)須改動(dòng)已有的類結(jié)構(gòu)即可序列化,這時(shí)非常贊的一個(gè)性質(zhì)。但是由于體積龐大,安裝復(fù)雜,如果只是簡(jiǎn)單的序列化,沒(méi)必要使用該方案,只有protobuf不能滿足你的需求時(shí),才應(yīng)該考慮該方案。
(1)安裝boost庫(kù)遇到的一系列問(wèn)題
安裝boost庫(kù)本事就是一項(xiàng)很費(fèi)時(shí)的工程,如果期間出現(xiàn)了各種錯(cuò)誤,更加耗時(shí)耗耐心。我們可以從官網(wǎng)下載Boost庫(kù)的二進(jìn)制源碼進(jìn)行安裝,安裝方法可以參考網(wǎng)絡(luò)或后面我給出的參考資料。
安裝過(guò)程如下:
首先解壓安裝包,如果是tar.gz用tar zxvf解壓,如果是tar.bz2用tar jxvf解壓,解壓后進(jìn)入解壓后的目錄,依次運(yùn)行以下命令:
./bootstrap.sh
sudo ./b2 install
注:這里沒(méi)有指定安裝路徑,在第二個(gè)命令可以加入--prefix指定安裝目錄。
安裝時(shí)的注意事項(xiàng):
注意1:要用root權(quán)限進(jìn)行安裝,否則會(huì)在安裝過(guò)程中報(bào)錯(cuò),提示權(quán)限不足。
注意2:boost庫(kù)的安裝依賴一些環(huán)境,通常有Python、bzip2和zlib,它們所在的軟件包分別為:
Ubuntu下:
zlib1g-dev
libbz2-dev
libpython2.7-dev (and libpython3.3-dev)
Fedora/Redhat下:
zlib-devel
libbz2-devel
python-devel (and python3-devel)
這也是安裝過(guò)程中報(bào)錯(cuò)的主要來(lái)源。
報(bào)錯(cuò)1:如果Python庫(kù)不完整,可能會(huì)報(bào)“ fatal error: pyconfig.h: No such file or directory compilation terminated.”或者“fatal error: patchlevel.h: No such file or directory”錯(cuò)誤。解決方法如下:
Fedora系統(tǒng):sudo yum install python-devel
Ubuntu系統(tǒng):sudo apt-get install python-dev
報(bào)錯(cuò)2:報(bào)錯(cuò) “ libs/IOStreams/src/bzip2.cpp:20:56: fatal error: bzlib.h: No such file or directory”,解決方案:
Fedora系統(tǒng):sudo yum install bzip2-devel
Ubuntu系統(tǒng)或Debian系統(tǒng):sudo apt-get install libbz2-dev
通常對(duì)于這些錯(cuò)誤,在Ubuntu系統(tǒng)下一般可以通過(guò)sudo apt-get install libboost-all-dev全部解決,但不一定行得通。
(2)安裝成功后,如果未指定安裝位置,那么默認(rèn)將會(huì)安裝到/usr/local/lib和/usr/local/include下,那么我們?cè)谑褂肂oost庫(kù)進(jìn)行編譯時(shí)就需要使用-L和-I參數(shù)加上具體的lib和include路徑,像下面這樣:
g++ -o test boost_test.cpp -I$BOOST_INCLUDE -L$BOOST_LIB -lboost_serialization
如果覺(jué)得每次都這樣很麻煩,那么可以將我們所要用到的lib和include文件加入到環(huán)境變量中,像下面這樣:
sudo cp /usr/local/lib/libboost_serialization.* /usr/lib
sudo cp -r /usr/local/include/boost /usr/include
然后在編譯時(shí)直接g++ -o test boost_test.cpp -lboost_serialization即可。
注意:boost下面有兩個(gè)序列化lib文件:ibboost_serialization.lib 和 libboost_wserialization.lib,那么這兩者有什么區(qū)別呢?
其實(shí)'w' 表示使用的是寬字符,例如 wchar_t。
(3)boost不盡人意的地方
基本類型指針很難序列化,例如int *array,官網(wǎng)上是這么說(shuō)的:
By default, data types designated primitive by Implementation Levelclass serialization trait are never tracked. If it is desired totrack a shared primitive object through a pointer (e.g. along used as a reference count), It should be wrappedin a class/struct so that it is an identifiable type.The alternative of changing the implementation level of alongwould affect alllongs serialized in the wholeprogram - probably not what one would intend.
也就是說(shuō)如果你想序列化原生類型的指針,需要給其加上struct或class使其變?yōu)轭愵愋驮傩蛄谢梢?jiàn)有些麻煩,這樣的需求往往也很頻繁,鑒于序列化機(jī)制的實(shí)現(xiàn)原理,boost庫(kù)暫時(shí)還不能很好的支持基本類型的指針序列化。
不能序列化變長(zhǎng)數(shù)組(variable-sized array),會(huì)報(bào)錯(cuò)說(shuō)變長(zhǎng)數(shù)組不是模板類類型。
(4)如果需要定義一個(gè)對(duì)象數(shù)組,如定義含有2個(gè)元素的class A對(duì)象數(shù)組,那么必須用A a[2]定義而不能用對(duì)象的指針A *a = new A[2]定義,這樣序列化a后默認(rèn)當(dāng)作一個(gè)A對(duì)象處理,因此只能存儲(chǔ)一個(gè)對(duì)象的值,后面的不會(huì)存儲(chǔ)。
(5)所謂boost很人性的非侵入性質(zhì)也有一定的條件:如果不想改動(dòng)原來(lái)的類,那么原來(lái)的類屬性必須是public的,這很容易解釋,因?yàn)槟惚仨?要能在別處訪問(wèn)到這些屬性并定義其序列化方式,當(dāng)然這也在其它地方暴露了類的結(jié)構(gòu),具有一定的劣勢(shì)。這樣的條件往往很難滿足,因?yàn)槲覀兌x的類屬性一般都 是private的,如果是這樣,且仍想要使用非侵入性質(zhì),那么需要在類中添加以下聲明來(lái)開(kāi)放訪問(wèn)給 serialization 庫(kù):
friend class boost::serialization::access;
這樣的方式比讓成員public更好。