C++內(nèi)存數(shù)據(jù)結(jié)構(gòu)與二進(jìn)制文件之間的序列化和反序列化方式
C++內(nèi)存數(shù)據(jù)結(jié)構(gòu)與二進(jìn)制文件之間的序列化和反序列化
應(yīng)用場(chǎng)景
許多后端檢索server啟動(dòng)時(shí)候需要從文件加載到內(nèi)存中構(gòu)建索引,這個(gè)過(guò)程往往會(huì)消耗比較多的時(shí)間,這樣會(huì)造成sever啟動(dòng)消耗比較多的時(shí)間,在存在多臺(tái)服務(wù)器的時(shí)候會(huì)更加明顯。
我們可以將夠構(gòu)建索引的過(guò)程獨(dú)立成一個(gè)單獨(dú)的進(jìn)程,此進(jìn)程實(shí)現(xiàn)的功能是根據(jù)原始文件構(gòu)建索引結(jié)構(gòu),并將索引結(jié)構(gòu)序列化到本地二進(jìn)制文件,Server在啟動(dòng)的時(shí)候只需要讀取二進(jìn)制文件就可以構(gòu)造出索引結(jié)構(gòu),可以大大提高啟動(dòng)速度。
示例代碼
io.hpp ,對(duì)std::ifstream 以及std::ofstream 的封裝,提供從vector序列化到二進(jìn)制文件和從二進(jìn)制文件反序列化到vector等接口
#ifndef IO_HPP #define IO_HPP #include <string> #include <vector> #include <fstream> class FileReader { public: FileReader(const std::string& filename) : input_stream(filename,std::ios::binary) { } /* Read count objects of type T into pointer dest */ template <typename T> void ReadInto(T *dest, const std::size_t count) { static_assert(std::is_trivially_copyable<T>::value, "bytewise reading requires trivially copyable type"); if (count == 0) return; const auto &result = input_stream.read(reinterpret_cast<char *>(dest), count * sizeof(T)); const std::size_t bytes_read = input_stream.gcount(); if (bytes_read != count * sizeof(T) && !result) { return; } } template <typename T> void ReadInto(std::vector<T> &target) { ReadInto(target.data(), target.size()); } template <typename T> void ReadInto(T &target) { ReadInto(&target, 1); } template <typename T> T ReadOne() { T tmp; ReadInto(tmp); return tmp; } std::uint32_t ReadElementCount32() { return ReadOne<std::uint32_t>(); } std::uint64_t ReadElementCount64() { return ReadOne<std::uint64_t>(); } template <typename T> void DeserializeVector(std::vector<T> &data) { const auto count = ReadElementCount64(); data.resize(count); ReadInto(data.data(), count); } private: std::ifstream input_stream; }; class FileWriter { public: FileWriter(const std::string& filename) : output_stream(filename,std::ios::binary) { } /* Write count objects of type T from pointer src to output stream */ template <typename T> void WriteFrom(const T *src, const std::size_t count) { static_assert(std::is_trivially_copyable<T>::value, "bytewise writing requires trivially copyable type"); if (count == 0) return; const auto &result = output_stream.write(reinterpret_cast<const char *>(src), count * sizeof(T)); } template <typename T> void WriteFrom(const T &target) { WriteFrom(&target, 1); } template <typename T> void WriteOne(const T tmp) { WriteFrom(tmp); } void WriteElementCount32(const std::uint32_t count) { WriteOne<std::uint32_t>(count); } void WriteElementCount64(const std::uint64_t count) { WriteOne<std::uint64_t>(count); } template <typename T> void SerializeVector(const std::vector<T> &data) { const auto count = data.size(); WriteElementCount64(count); return WriteFrom(data.data(), count); } private: std::ofstream output_stream; }; #endif
binary_io.cpp
#include "io.hpp" #include <iostream> struct Data { int a; double b; friend std::ostream& operator<<(std::ostream& out,const Data& data) { out << data.a << "," << data.b; return out; } }; template<typename T> void printData(const std::vector<T>& data_vec) { for (const auto data : data_vec) { std::cout << "{" << data << "} "; } std::cout << std::endl; } template<typename T> void serializeVector(const std::string& filename,const std::vector<T>& data_vec) { FileWriter file_writer(filename); file_writer.SerializeVector<T>(data_vec); } template<typename T> void deserializeVector(const std::string& filename,std::vector<T>& data_vec) { FileReader file_reader(filename); file_reader.DeserializeVector<T>(data_vec); } int main() { std::vector<Data> vec1 = {{1,1.1},{2,2.2},{3,3.3},{4,4.4}}; std::cout << "before write to binary file.\n"; printData(vec1); const std::string filename = "vector_data"; std::cout << "serialize vector to binary file.\n"; serializeVector<Data>(filename,vec1); std::vector<Data> vec2; deserializeVector<Data>(filename,vec2); std::cout << "vector read from binary file.\n"; printData(vec2); return 0; }
編譯代碼
g++ -std=c++11 binary_io.cpp -o binary_io
執(zhí)行程序
./binary_io
執(zhí)行結(jié)果
程序?qū)?nèi)存中vector 數(shù)據(jù)寫(xiě)入二進(jìn)制文件,并從二進(jìn)制文件中反序列化到一個(gè)新的vector。
可以看到序列化前和序列化后的結(jié)果一致。
注意
序列化到文件的數(shù)據(jù)結(jié)構(gòu)需要滿(mǎn)足 is_trivially_copyable。std::is_trivially_copyable 在c++11 引入,TriviallyCopyable類(lèi)型對(duì)象有以下性質(zhì)
- 每個(gè)拷貝構(gòu)造函數(shù)是trivial 或者是deleted
- 每個(gè)移動(dòng)構(gòu)造函數(shù)是trivial 或者是deleted
- 每個(gè)拷貝賦值運(yùn)算符是trivial 或者是deleted
- 每個(gè)移動(dòng)賦值運(yùn)算符是trivial 或者是deleted
- 以上至少有一個(gè)是non-deleted
- 析構(gòu)函數(shù)是trivial 并且non-deleted
對(duì)于is_trivially_copyable 類(lèi)型對(duì)象的性質(zhì),解釋如下
Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write()/std::ifstream::read(). In general, a trivially copyable type is any type for which the underlying bytes can be copied to an array of char or unsigned char and into a new object of the same type, and the resulting object would have the same value as the original
只有滿(mǎn)足trivially-copyable的對(duì)象才可以保證序列化到二進(jìn)制文件后, 從二進(jìn)制文件反序列化到內(nèi)存后的值保持不變。
序列化與反序列化(其實(shí)就是一種將各種數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制流與二進(jìn)制流的讀取的概念)
- 序列化:
將數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換稱(chēng)為二進(jìn)制數(shù)據(jù)流或者文本流的過(guò)程。序列化后的數(shù)據(jù)方便在網(wǎng)絡(luò)上傳輸和在硬盤(pán)上存儲(chǔ)。
- 反序列化:
與序列化相反,是將二進(jìn)制數(shù)據(jù)流或者文本流轉(zhuǎn)換稱(chēng)為易于處理和閱讀的數(shù)據(jù)結(jié)構(gòu)的過(guò)程。
本質(zhì)其實(shí)還是一種協(xié)議,一種數(shù)據(jù)格式,方便數(shù)據(jù)的存儲(chǔ)和傳輸。
為什么需要序列化?
我們知道,計(jì)算機(jī)世界往往是根據(jù)二進(jìn)制來(lái)區(qū)分?jǐn)?shù)據(jù)的,例如一個(gè)字節(jié)、兩個(gè)字節(jié)、三個(gè)字節(jié)等等,但是,由于在內(nèi)存中或者磁盤(pán)上,或者平臺(tái)的環(huán)境不同,為了方便數(shù)據(jù)在不同的地方能夠具有相同的含義,我們需要將數(shù)據(jù)轉(zhuǎn)換為一種大家都能識(shí)別的格式,二進(jìn)制或者編碼格式是大家都認(rèn)同的方式,而序列化正好是將一種數(shù)據(jù)格式轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)流的過(guò)程或者方法,那么該數(shù)據(jù)結(jié)構(gòu)就能夠在任何地方保持其原有的含義,這就是序列化的意義。
什么是編碼?與序列化的區(qū)別?
編碼也是一種約定數(shù)據(jù)的含義的方式。與序列化不同,其約定的是更底層一些的數(shù)據(jù)含義,例如字符的表示,有ASCII、UTF-8、GBK等等,例如整數(shù)的表示,1=00000001一個(gè)字節(jié),編碼約定了數(shù)據(jù)格式的最小單元,序列化是依賴(lài)于字符編碼之上的一種數(shù)據(jù)格式。
二進(jìn)制流和文本流的區(qū)別:
二進(jìn)制流是以二進(jìn)制為最小單位進(jìn)行編碼,例如多少個(gè)bit代表什么,文本流是以字符來(lái)進(jìn)行編碼,約定好多少個(gè)字節(jié)(通常是以字節(jié)為單位)代表什么。
最后附一個(gè)C/C++序列化庫(kù),boost serialization。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++實(shí)現(xiàn)獲取時(shí)間戳和計(jì)算運(yùn)行時(shí)長(zhǎng)
這篇文章主要為大家詳細(xì)介紹了如何使用C++實(shí)現(xiàn)獲取時(shí)間戳和計(jì)算運(yùn)行時(shí)長(zhǎng)功能,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-12-12C++實(shí)現(xiàn)拼圖游戲代碼(graphics圖形庫(kù))
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)拼圖游戲代碼,帶有g(shù)raphics圖形庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05基于QT的TCP通信服務(wù)的實(shí)現(xiàn)
在項(xiàng)目開(kāi)發(fā)過(guò)程中,很多地方都會(huì)用到TCP通信,本文主要介紹了基于QT的TCP通信服務(wù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05C語(yǔ)言實(shí)現(xiàn)大數(shù)值金額大寫(xiě)轉(zhuǎn)換的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)大數(shù)值金額大寫(xiě)轉(zhuǎn)換的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-03-03