最經常使用的兩種C++序列化方案的使用心得(protobuf和boost serialization)

導讀

1. 什麼是序列化?html

2. 爲何要序列化?好處在哪裏?python

3. C++對象序列化的四種方法linux

4. 最經常使用的兩種序列化方案使用心得ios

 

正文

1. 什麼是序列化?

程序員在編寫應用程序的時候每每須要將程序的某些數據存儲在內存中,而後將其寫入某個文件或是將它傳輸到網絡中的另外一臺計算機上以實現通信。這個將 程序數據轉化成能被存儲並傳輸的格式的過程被稱爲「序列化」(Serialization),而它的逆過程則可被稱爲「反序列化」 (Deserialization)。c++

簡單來講,序列化就是將對象實例的狀態轉換爲可保持或傳輸的格式的過程。與序列化相對的是反序列化,它根據流重構對象。這兩個過程結合起來,能夠輕 鬆地存儲和傳輸數據。例如,能夠序列化一個對象,而後使用 HTTP 經過 Internet 在客戶端和服務器之間傳輸該對象。程序員

 

總結算法

序列化:將對象變成字節流的形式傳出去。bootstrap

反序列化:從字節流恢復成原來的對象。api

 

2. 爲何要序列化?好處在哪裏?

簡單來講,對象序列化一般用於兩個目的:  數組

(1) 將對象存儲於硬盤上  ,便於之後反序列化使用

(2)在網絡上傳送對象的字節序列

 

對象序列化的好處在哪裏?網絡傳輸方面的便捷性、靈活性就不說了,這裏舉個咱們常常可能發生的需求:你 有一個數據結構,裏面存儲的數據是通過不少其它數據經過很是複雜的算法生成的,因爲數據量很大,算法又複雜,所以生成該數據結構所用數據的時間可能要好久 (也許幾個小時,甚至幾天),生成該數據結構後又要用做其它的計算,那麼你在調試階段,每次運行個程序,就光生成數據結構就要花上這麼長的時間,無疑代價 是很是大的。若是你肯定生成數據結構的算法不會變或不常變,那麼就能夠經過序列化技術生成數據結構數據存儲到磁盤上,下次從新運行程序時只須要從磁盤上讀 取該對象數據便可,所花費時間也就讀一個文件的時間,可想而知是多麼的快,節省了咱們的開發時間。

 

3. C++對象序列化的四種方法

將C++對象進行序列化的方法通常有四種,下面分別介紹:

 

3.1 Google Protocol Buffers(protobuf)

Google Protocol Buffers (GPB)是Google內部使用的數據編碼方式,旨在用來代替XML進行數據交換。可用於數據序列化與反序列化。主要特性有:

  • 高效
  • 語言中立(Cpp, Java, Python)
  • 可擴展

官方文檔

 

3.2 Boost.Serialization

Boost.Serialization能夠建立或重建程序中的等效結構,並保存爲二進制數據、文本數據、XML或者有用戶自定義的其餘文件。該庫具備如下吸引人的特性:

  • 代碼可移植(實現僅依賴於ANSI C++)。
  • 深度指針保存與恢復。
  • 能夠序列化STL容器和其餘經常使用模版庫。
  • 數據可移植。
  • 非入侵性。

 

3.3 MFC Serialization

Windows平臺下可以使用MFC中的序列化方法。MFC 對 CObject 類中的序列化提供內置支持。所以,全部從 CObject 派生的類均可利用 CObject 的序列化協議。

MSDN中的介紹

 

3.4 .Net Framework

.NET的運行時環境用來支持用戶定義類型的流化的機制。它在此過程當中,先將對象的公共字段和私有字段以及類的名稱(包括類所在的程序集)轉換爲字節流,而後再把字節流寫入數據流。在隨後對對象進行反序列化時,將建立出與原對象徹底相同的副本。

 

3.5 簡單總結

這幾種序列化方案各有優缺點,各有本身的適用場景。其中MFC和.Net框架的方法適用範圍很窄,只適用於Windows下,且.Net框架方法還須要.Net的運行環境。參考文獻1從序列化時間、反序列化時間和產生數據文件大小這幾個方面比較了前三種序列化方案,得出結論以下(僅供參考):

  • Google Protocol Buffers效率較高,可是數據對象必須預先定義,並使用protoc編譯,適合要求效率,容許自定義類型的內部場合使用。
  • Boost.Serialization 使用靈活簡單,並且支持標準C++容器。
  • 相比而言,MFC的效率較低,可是結合MSVS平臺使用最爲方便。

爲了考慮平臺的移植性、適用性和高效性,推薦你們使用Google的protobuf和Boost的序列化方案,下面介紹我使用這兩種方案的心得及注意事項。

 

4. 最經常使用的兩種序列化方案使用心得

關於這兩種方案的具體使用和示例沒什麼好寫的,由於優秀的參考資料不少,請看後面給出的相關參考資料,這裏只給出我使用時的一些心得,方便你們在選擇序列化方案時有個正確的參考,避免選擇錯誤,浪費時間。

 

4.1 Google Protocol Buffers

protobuf相對而言效率應該是最高的,不論是安裝效率仍是使用效率,protobuf都很高效,並且protobuf不只用於C++序列化,還可用於Java和Python的序列化,使用範圍很廣。但在使用過程當中要注意兩個問題:

 

(1)protobuf支持的數據類型不是很豐富

protobuf屬於輕量級的,所以不能支持太多的數據類型,下面是protobuf支持的基本類型列表,通常都能知足需求,不過在選擇方案以前,仍是先看看是否都能支持,以避免前功盡棄。一樣該表也值得收藏,做爲咱們在定義類型時作參考。

.proto type

c++

notes

double

double

 

float

float

 

int32

int32

使用可變長編碼方式,負數時不夠高效,應該使用sint32

int64

int64

同上

uint32

uint32

使用可變長編碼方式

uint64

uint64

同上

sint32

int32

使用可變長編碼方式,有符號的整型值,編碼時比一般的int32高效

sint64

sint64

同上

fixed32

uint32

老是4個字節,若是數值老是比2^28大的話,這個類型會比uint32高效

fixed64

uint64

老是8個字節,若是數值老是比2^56大的話,這個類型會比uint64高效

sfixed32

int32

老是4個字節

sfixed64

int64

老是8個字節

bool

bool

 

string

string

一個字符串必須是utf-8編碼或者7-bit的ascii編碼的文本

bytes

string

可能包含任意順序的字節數據

 

(2)protobuf不支持二維數組(指針),不支持STL容器序列化

這個缺陷挺大,由於稍複雜點的數據結構或類結構裏出現二維數組、二維指針和STL容器(set、list、map等)很頻繁,但由於 protobuf簡單的實現機制,只支持一維數組和指針(用repeated修飾符修飾),不能使用repeated repeated來支持二維數組, 也不支持STL,所以在選擇該方案以前,必定 要確保你的數據結構裏沒有這些不支持的類型。

 

(3)protobuf嵌套後會改變類名稱

protobuf支持類的嵌套,即在一個自定義類型中能夠定義另外一個自定義類型,但注意嵌套的自定義類型在通過protobuf處理後生成的類名稱並非你定義的類名稱,而是加上了外層的類名稱做爲前綴,下面舉一個簡單的例子:

複製代碼
    message DFA {  
        required int32 _size = 1;  
       
        message accept_pair {  
          required bool is_accept_state = 1;  
          required bool is_strict_end = 2;  
          optional string app_name = 3;  
        }  
       
        repeated accept_pair accept_states = 2;  
    }  
複製代碼

 

那麼嵌套中的accept_pair 生成後的類不是accept_pair 而是DFA_accept_pair 。若是不想改類名稱,將accept_pair 拿到外面與DFA平行定義便可。

 

4.2 Boost.Serialization

Boost庫是個很龐大的庫,功能很是豐富,序列化只是其中的一個小分支,但爲了使用Boost的序列化方案,你須要安裝整個Boost庫,所花費的磁盤空間和時間都不少,一樣支持的序列化功能也很強大,既支持二維數組(指針),也支持STL容器,更不須要咱們用某種特殊的格式從新定義咱們的類結構,其非侵入的性質使得咱們無須改動已有的類結構便可序列化,這時很是讚的一個性質。可是因爲體積龐大,安裝複雜,若是隻是簡單的序列化,不必使用該方案,只有protobuf不能知足你的需求時,才應該考慮該方案。

 

(1)安裝boost庫遇到的一系列問題

安裝boost庫本事就是一項很費時的工程,若是期間出現了各類錯誤,更加耗時耗耐心。咱們能夠從官網下載Boost庫的二進制源碼進行安裝,安裝方法能夠參考網絡或後面我給出的參考資料。

安裝過程以下:

首先解壓安裝包,若是是tar.gz用tar zxvf解壓,若是是tar.bz2用tar jxvf解壓,解壓後進入解壓後的目錄,依次運行如下命令:

./bootstrap.sh
sudo ./b2 install

注:這裏沒有指定安裝路徑,在第二個命令能夠加入--prefix指定安裝目錄。

 

安裝時的注意事項:

注意1:要用root權限進行安裝,不然會在安裝過程當中報錯,提示權限不足。

注意2:boost庫的安裝依賴一些環境,一般有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) 

 

這也是安裝過程當中報錯的主要來源。

報錯1:若是Python庫不完整,可能會報「 fatal error: pyconfig.h: No such file or directory compilation terminated.」或者「fatal error: patchlevel.h: No such file or directory」錯誤。解決方法以下:

Fedora系統:sudo yum install python-devel

Ubuntu系統:sudo apt-get  install python-dev

 

報錯2:報錯 「 libs/iostreams/src/bzip2.cpp:20:56: fatal error: bzlib.h: No such file or directory」,解決方案:

Fedora系統:sudo yum install bzip2-devel

Ubuntu系統或Debian系統:sudo apt-get install libbz2-dev

 

一般對於這些錯誤,在Ubuntu系統下通常能夠經過sudo apt-get install libboost-all-dev所有解決,但不必定行得通。

 

(2)安裝成功後,若是未指定安裝位置,那麼默認將會安裝到/usr/local/lib和/usr/local/include下,那麼咱們在使用Boost庫進行編譯時就須要使用-L和-I參數加上具體的lib和include路徑,像下面這樣:

g++ -o test boost_test.cpp -I$BOOST_INCLUDE -L$BOOST_LIB -lboost_serialization

若是以爲每次都這樣很麻煩,那麼能夠將咱們所要用到的lib和include文件加入到環境變量中,像下面這樣:

sudo cp /usr/local/lib/libboost_serialization.* /usr/lib
sudo cp -r /usr/local/include/boost /usr/include

而後在編譯時直接g++ -o test boost_test.cpp -lboost_serialization便可。

注意:boost下面有兩個序列化lib文件:ibboost_serialization.lib 和 libboost_wserialization.lib,那麼這二者有什麼區別呢?

其實'w' 表示使用的是寬字符,例如 wchar_t。

 

(3)boost不盡人意的地方

  • 基本類型指針很難序列化,例如int *array,官網上是這麼說的:
    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. a long 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 a longwould affect all longs serialized in the wholeprogram - probably not what one would intend.
    也就是說若是你想序列化原生類型的指針,須要給其加上struct或class使其變爲類類型再序列化,可見有些麻煩,這樣的需求每每也很頻繁,鑑於序列化機制的實現原理,boost庫暫時還不能很好的支持基本類型的指針序列化。
  • 不能序列化變長數組(variable-sized array),會報錯說變長數組不是模板類類型。

 

(4)若是須要定義一個對象數組,如定義含有2個元素的class A對象數組,那麼必須用A a[2]定義而不能用對象的指針A *a = new A[2]定義,這樣序列化a後默認看成一個A對象處理,所以只能存儲一個對象的值,後面的不會存儲。

 

(5)所謂boost很人性的非侵入性質也有必定的條件:若是不想改動原來的類,那麼原來的類屬性必須是public的,這很容易解釋,由於你必須 要能在別處訪問到這些屬性並定義其序列化方式,固然這也在其它地方暴露了類的結構,具備必定的劣勢。這樣的條件每每很難知足,由於咱們定義的類屬性通常都 是private的,若是是這樣,且仍想要使用非侵入性質,那麼須要在類中添加如下聲明來開放訪問給 serialization 庫:

friend class boost::serialization::access; 

這樣的方式比讓成員public更好。

 

參考資料

相關文章
相關標籤/搜索