C++數(shù)據(jù)序列化方式(自定義結(jié)構(gòu)體的保存和讀取)
C++數(shù)據(jù)序列化
碰到一個需求,結(jié)構(gòu)體數(shù)據(jù)需要保存下來,以便下次程序打開后再次加載。結(jié)構(gòu)體存在嵌套。
查找資料,確認(rèn)可以通過文件的讀寫進(jìn)行操作,F(xiàn)ILE,fread和fwrite可以實現(xiàn),以下是測試代碼(使用模板實現(xiàn)相關(guān)功能)
核心部分是
template <class T> void write_dataToFile( T *t, const char *filePath) template <class T> void read_dataFromFile(T* t, const char *filePath)
兩個文件,主要是利用了FILE的讀寫操作,注意,此方法可能存在瑕疵,文件的size依賴于平臺,如果在A平臺生成(寫)的文件,在B平臺可能會解析(讀)錯誤。
#include <QApplication> #include <QtDebug> #include <stdio.h> #include <errno.h> #include <string.h> enum ENUMT{ DI = 0, DO, DP }; struct Info_C{ QString name = "1212"; double age = 12.32323; }; struct Info{ QString name = "ddsdsd"; int age = 0; ENUMT enumT = DI; Info_C info_c; }; template <class T> void write_dataToFile( T *t, const char *filePath) { FILE *fp = nullptr; fp = fopen(filePath, "w+"); if (nullptr == fp) { printf("open failure ,filePath : %s, errno: %d", filePath, errno); qDebug("open failure ,filePath : %s, errno: %d", filePath, errno); return; } fwrite(t, sizeof(T), 1, fp); fclose(fp); } template <class T> void read_dataFromFile(T* t, const char *filePath) { FILE *fp = fopen(filePath, "r"); if (nullptr == fp) { printf("open failure ,filePath : %s, errno: %d", filePath, errno); qDebug("open failure ,filePath : %s, errno: %d", filePath, errno); return; } fread(t, sizeof(T), 1, fp); fclose(fp); } using namespace hv; int main(int argc, char *argv[]) { //讀寫基本類型 Info writeInfo; writeInfo.name = "baseType"; writeInfo.age = 18; writeInfo.enumT = DO; write_dataToFile(&writeInfo,"./info.txt"); Info readInfo; read_dataFromFile(&readInfo,"./info.txt"); qDebug()<<readInfo.name<<readInfo.age<<readInfo.enumT; //讀寫容器,vector QVector<QString> vec; vec.push_back("vec_base1"); vec.push_back("vec_base2"); vec.push_back("vec_base3"); write_dataToFile(&vec,"./info1.txt"); QVector<QString> vec11; read_dataFromFile(&vec11,"./info1.txt"); qDebug("read:%d\n", vec11.count()); if(vec11.count() > 2) qDebug()<<vec11.at(2); //讀寫容器結(jié)構(gòu)體-包含嵌套 QVector<Info> vec_stu; Info stu; stu.name = "vec_stu1"; stu.age = 18; stu.enumT = DO; vec_stu.push_back(stu); Info stu1; stu1.name = "vec_stu2"; stu1.age = 100; stu1.enumT = DI; stu1.info_c.age = 43.434; stu1.info_c.name = "vec_stu_stu"; vec_stu.push_back(stu1); write_dataToFile(&vec_stu,"./info11.txt"); QVector<Info> vec_stu11; read_dataFromFile(&vec_stu11,"./info11.txt"); qDebug("read vector stu:%d\n", vec_stu11.count()); if(vec_stu11.count() > 1){ qDebug()<<vec_stu11.at(1).name<<vec_stu11.at(1).info_c.age<<vec_stu11.at(1).info_c.name; } return 0; }
結(jié)果示意圖,可正常的進(jìn)行讀取。
常用的c++序列化方法
1、 什么是序列化?
程序員在編寫應(yīng)用程序的時候往往需要將程序的某些數(shù)據(jù)存儲在內(nèi)存中,然后將其寫入某個文件或是將它傳輸?shù)骄W(wǎng)絡(luò)中的另一臺計算機(jī)上以實現(xiàn)通訊。這個將 程序數(shù)據(jù)轉(zhuǎn)化成能被存儲并傳輸?shù)母袷降倪^程被稱為“序列化”(Serialization),而它的逆過程則可被稱為“反序列化” (Deserialization)。
簡單來說,序列化就是將對象實例的狀態(tài)轉(zhuǎn)換為可保持或傳輸?shù)母袷降倪^程。與序列化相對的是反序列化,它根據(jù)流重構(gòu)對象。這兩個過程結(jié)合起來,可以輕 松地存儲和傳輸數(shù)據(jù)。例如,可以序列化一個對象,然后使用 HTTP 通過 Internet 在客戶端和服務(wù)器之間傳輸該對象。
總結(jié):
序列化:將對象變成字節(jié)流的形式傳出去。
反序列化:從字節(jié)流恢復(fù)成原來的對象。
序列化簡化了對象的保存和載入,為對象提供了持久性。但是,序列化本身仍具有一定的局限性。
由于序列化一次從文件中載入所有對象,因此,它不適合于大文件編輯器和數(shù)據(jù)庫。對于數(shù)據(jù)庫和大文件編輯器,它們每次只是從文件中讀入一部分。此時,就不應(yīng)該采用文檔的序列化機(jī)制來直接讀取和保存文件了。
另外,使用外部文件格式(預(yù)先定義的文件格式而不是本應(yīng)用程序定義的文件格式)的程序一般也不使用文檔的序列化。
2、 為什么要序列化?好處在哪里?
簡單來說,對象序列化通常用于兩個目的:
(1) 將對象存儲于硬盤上 ,便于以后反序列化使用
(2)在網(wǎng)絡(luò)上傳送對象的字節(jié)序列
對象序列化的好處在哪里?網(wǎng)絡(luò)傳輸方面的便捷性、靈活性就不說了,這里舉個我們經(jīng)??赡馨l(fā)生的需求:你 有一個數(shù)據(jù)結(jié)構(gòu),里面存儲的數(shù)據(jù)是經(jīng)過很多其它數(shù)據(jù)通過非常復(fù)雜的算法生成的,由于數(shù)據(jù)量很大,算法又復(fù)雜,因此生成該數(shù)據(jù)結(jié)構(gòu)所用數(shù)據(jù)的時間可能要很久 (也許幾個小時,甚至幾天),生成該數(shù)據(jù)結(jié)構(gòu)后又要用作其它的計算,那么你在調(diào)試階段,每次運行個程序,就光生成數(shù)據(jù)結(jié)構(gòu)就要花上這么長的時間,無疑代價 是非常大的。
如果你確定生成數(shù)據(jù)結(jié)構(gòu)的算法不會變或不常變,那么就可以通過序列化技術(shù)生成數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)存儲到磁盤上,下次重新運行程序時只需要從磁盤上讀 取該對象數(shù)據(jù)即可,所花費時間也就讀一個文件的時間,可想而知是多么的快,節(jié)省了我們的開發(fā)時間。
3、最常用的兩種序列化方案使用心得
3.1、Google Protocol Buffers
protobuf相對而言效率應(yīng)該是最高的,不管是安裝效率還是使用效率,protobuf都很高效,而且protobuf不僅用于C++序列化,還可用于Java和Python的序列化,使用范圍很廣。但在使用過程中要注意兩個問題:
(1)protobuf支持的數(shù)據(jù)類型不是很豐富
protobuf屬于輕量級的,因此不能支持太多的數(shù)據(jù)類型,下面是protobuf支持的基本類型列表,一般都能滿足需求,不過在選擇方案之前,還是先看看是否都能支持,以免前功盡棄。同樣該表也值得收藏,作為我們在定義類型時做參考。
(2)protobuf不支持二維數(shù)組(指針),不支持STL容器序列化
這個缺陷挺大,因為稍復(fù)雜點的數(shù)據(jù)結(jié)構(gòu)或類結(jié)構(gòu)里出現(xiàn)二維數(shù)組、二維指針和STL容器(set、list、map等)很頻繁,但因為 protobuf簡單的實現(xiàn)機(jī)制,只支持一維數(shù)組和指針(用repeated修飾符修飾),不能使用repeated repeated來支持二維數(shù)組, 也不支持STL,因此在選擇該方案之前,一定 要確保你的數(shù)據(jù)結(jié)構(gòu)里沒有這些不支持的類型。
(3)protobuf嵌套后會改變類名稱
protobuf支持類的嵌套,即在一個自定義類型中可以定義另一個自定義類型,但注意嵌套的自定義類型在經(jīng)過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平行定義即可。
3.2 Boost.Serialization
Boost庫是個很龐大的庫,功能非常豐富,序列化只是其中的一個小分支,但為了使用Boost的序列化方案,你需要安裝整個Boost庫,所花費的磁盤空間和時間都很多,同樣支持的序列化功能也很強(qiáng)大,既支持二維數(shù)組(指針),也支持STL容器,更不需要我們用某種特殊的格式重新定義我們的類結(jié)構(gòu),其非侵入的性質(zhì)使得我們無須改動已有的類結(jié)構(gòu)即可序列化,這時非常贊的一個性質(zhì)。但是由于體積龐大,安裝復(fù)雜,如果只是簡單的序列化,沒必要使用該方案,只有protobuf不能滿足你的需求時,才應(yīng)該考慮該方案。
- text_oarchive:文本序列化
- binary_oarchive:二進(jìn)制系列化
例子:
#include <iostream> #include <boost\archive\text_oarchive.hpp> #include <boost\archive\text_iarchive.hpp> //#include <boost\archive\binary_oarchive.hpp> //#include <boost\archive\binary_iarchive.hpp> #include <string> #include <fstream> void Save() { ?? ?/*打開Test.bin 此種方法是利用構(gòu)造函數(shù)打開*/ ?? ?std::ofstream file("Test.bin"); ?? ?/*定義oa為二進(jìn)制存儲類型*/ ?? ?boost::archive::text_oarchive oa(file); ?? ?//boost::archive::binary_oarchive oa(file); ?? ?int nNum = 0; ?? ?std::cout << "輸入int型" << std::endl; ?? ?std::cin >> nNum; ?? ?float fFloat = 0.0f; ?? ?std::cout << "輸入float型" << std::endl; ?? ?std::cin >> fFloat; ?? ?oa << nNum << fFloat; ?? ?file.close(); } void Load() { ?? ?std::ifstream file("Test.bin"); ?? ?/*定義ia為二進(jìn)制讀取類型*/ ?? ?boost::archive::text_iarchive ia(file); ?? ?//boost::archive::binary_iarchive ia(file); ?? ?/*讀取順序應(yīng)和存儲順序相同 類型也需要相同*/ ?? ?int n = 0; ?? ?float f = 0.0f; ?? ?ia >> n >> f; ?? ?std::cout << n << " " << f << std::endl; ?? ?file.close(); } int main() { ?? ?int nTemp = 0; ?? ?std::cout << "1.輸入并存儲 ?2.讀取并顯示" << std::endl; ?? ?std::cin >> nTemp; ?? ?switch (nTemp) ?? ?{ ?? ?case 1: ?? ??? ?Save(); ?? ??? ?break; ?? ?case 2: ?? ??? ?Load(); ?? ??? ?break; ?? ?default: ?? ??? ?break; ?? ?} ?? ?while (1); ?? ?return 0; }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++實現(xiàn)LeetCode(29.兩數(shù)相除)
這篇文章主要介紹了C++實現(xiàn)LeetCode(29.兩數(shù)相除),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07