導讀
1. 什么是序列化?
2. 為什么要序列化?優點在哪里?
3. C++對象序列化的四種方法
4. 最經常使用的兩種序列化方案使用心得
正文
1. 什么是序列化?
程序猿在編寫應用程序的時候往往須要將程序的某些數據存儲在內存中,然后將其寫入某個文件或是將它傳輸到網絡中的還有一台計算機上以實現通訊。這個將程序數據轉化成能被存儲並傳輸的格式的過程被稱為“序列化”(Serialization),而它的逆過程則可被稱為“反序列化”(Deserialization)。
簡單來說,序列化就是將對象實例的狀態轉換為可保持或傳輸的格式的過程。與序列化相對的是反序列化,它依據流重構對象。這兩個過程結合起來,能夠輕松地存儲和數據傳輸。比如,能夠序列化一個對象,然后使用 HTTP 通過 Internet 在client和server之間傳輸該對象。
總結
序列化:將對象變成字節流的形式傳出去。
反序列化:從字節流恢復成原來的對象。
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 的序列化協議。
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庫本事就是一項非常費時的project,假設期間出現了各種錯誤,更加耗時耗耐心。我們能夠從官網下載Boost庫的二進制源代碼進行安裝,安裝方法請參考網絡或后面我給出的參考資料,以下給出安裝時的注意事項:
注意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.”錯誤。解決方法例如以下:
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. alongused 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的,這非常easy解釋,由於你必需要能在別處訪問到這些屬性並定義其序列化方式,當然這也在其他地方暴露了類的結構,具有一定的劣勢。這種條件往往非常難滿足,由於我們定義的類屬性一般都是private的,假設是這樣,且仍想要使用非侵入性質,那么需要在類中加入下面聲明來開放訪問給 serialization 庫:
friend class boost::serialization::access;
這種方式比讓成員public更好。
