1. 程式人生 > >最常用的兩種C++序列化方案的使用心得(protobuf和boost serialization)

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

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

導讀

1. 什麼是序列化?

2. 為什麼要序列化?好處在哪裡?

3. C++物件序列化的四種方法

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

正文

1. 什麼是序列化?

程式設計師在編寫應用程式的時候往往需要將程式的某些資料儲存在記憶體中,然後將其寫入某個檔案或是將它傳輸到網路中的另一臺計算機上以實現通訊。這個將 程式資料轉化成能被儲存並傳輸的格式的過程被稱為“序列化”(Serialization),而它的逆過程則可被稱為“反序列化” (Deserialization)。

簡單來說,序列化就是將物件例項的狀態轉換為可保持或傳輸的格式的過程。與序列化相對的是反序列化,它根據流重構物件。這兩個過程結合起來,可以輕 鬆地儲存和傳輸資料。例如,可以序列化一個物件,然後使用 HTTP 通過 Internet 在客戶端和伺服器之間傳輸該物件。

總結

序列化:將物件變成位元組流的形式傳出去。

反序列化:從位元組流恢復成原來的物件。

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支援的基本型別列表,一般都能滿足需求,不過在選擇方案之前,還是先看看是否都能支援,以免前功盡棄。同樣該表也值得收藏,作為我們在定義型別時做參考。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20190725204956840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o0MDQ3MzkxNDA=,size_16,color_FFFFFF,t_70)
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20190725205010167.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3o0MDQ3MzkxNDA=,size_16,color_FFFFFF,t_70)

(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 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庫是個很龐大的庫,功能非常豐富,序列化只是其中的一個小分支,但為了使用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. 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.

也就是說如果你想序列化原生型別的指標,需要給其加上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更好。

文末也給大家,分享主要有C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK技術,面試技巧方面的資料技術討論。

感興趣的朋友可