C++內(nèi)存數(shù)據(jù)結(jié)構(gòu)與二進(jìn)制文件之間的序列化和反序列化方式
C++內(nèi)存數(shù)據(jù)結(jié)構(gòu)與二進(jìn)制文件之間的序列化和反序列化
應(yīng)用場景
許多后端檢索server啟動(dòng)時(shí)候需要從文件加載到內(nèi)存中構(gòu)建索引,這個(gè)過程往往會消耗比較多的時(shí)間,這樣會造成sever啟動(dòng)消耗比較多的時(shí)間,在存在多臺服務(wù)器的時(shí)候會更加明顯。
我們可以將夠構(gòu)建索引的過程獨(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 ,對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;
};
#endifbinary_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ù)寫入二進(jìn)制文件,并從二進(jìn)制文件中反序列化到一個(gè)新的vector。
可以看到序列化前和序列化后的結(jié)果一致。
注意
序列化到文件的數(shù)據(jù)結(jié)構(gòu)需要滿足 is_trivially_copyable。std::is_trivially_copyable 在c++11 引入,TriviallyCopyable類型對象有以下性質(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
對于is_trivially_copyable 類型對象的性質(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
只有滿足trivially-copyable的對象才可以保證序列化到二進(jìn)制文件后, 從二進(jìn)制文件反序列化到內(nèi)存后的值保持不變。
序列化與反序列化(其實(shí)就是一種將各種數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制流與二進(jìn)制流的讀取的概念)
- 序列化:
將數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換稱為二進(jìn)制數(shù)據(jù)流或者文本流的過程。序列化后的數(shù)據(jù)方便在網(wǎng)絡(luò)上傳輸和在硬盤上存儲。
- 反序列化:
與序列化相反,是將二進(jìn)制數(shù)據(jù)流或者文本流轉(zhuǎn)換稱為易于處理和閱讀的數(shù)據(jù)結(jié)構(gòu)的過程。
本質(zhì)其實(shí)還是一種協(xié)議,一種數(shù)據(jù)格式,方便數(shù)據(jù)的存儲和傳輸。
為什么需要序列化?
我們知道,計(jì)算機(jī)世界往往是根據(jù)二進(jìn)制來區(qū)分?jǐn)?shù)據(jù)的,例如一個(gè)字節(jié)、兩個(gè)字節(jié)、三個(gè)字節(jié)等等,但是,由于在內(nèi)存中或者磁盤上,或者平臺的環(huán)境不同,為了方便數(shù)據(jù)在不同的地方能夠具有相同的含義,我們需要將數(shù)據(jù)轉(zhuǎn)換為一種大家都能識別的格式,二進(jìn)制或者編碼格式是大家都認(rèn)同的方式,而序列化正好是將一種數(shù)據(jù)格式轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)流的過程或者方法,那么該數(shù)據(jù)結(jié)構(gòu)就能夠在任何地方保持其原有的含義,這就是序列化的意義。
什么是編碼?與序列化的區(qū)別?
編碼也是一種約定數(shù)據(jù)的含義的方式。與序列化不同,其約定的是更底層一些的數(shù)據(jù)含義,例如字符的表示,有ASCII、UTF-8、GBK等等,例如整數(shù)的表示,1=00000001一個(gè)字節(jié),編碼約定了數(shù)據(jù)格式的最小單元,序列化是依賴于字符編碼之上的一種數(shù)據(jù)格式。
二進(jìn)制流和文本流的區(qū)別:
二進(jìn)制流是以二進(jìn)制為最小單位進(jìn)行編碼,例如多少個(gè)bit代表什么,文本流是以字符來進(jìn)行編碼,約定好多少個(gè)字節(jié)(通常是以字節(jié)為單位)代表什么。
最后附一個(gè)C/C++序列化庫,boost serialization。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++實(shí)現(xiàn)獲取時(shí)間戳和計(jì)算運(yùn)行時(shí)長
這篇文章主要為大家詳細(xì)介紹了如何使用C++實(shí)現(xiàn)獲取時(shí)間戳和計(jì)算運(yùn)行時(shí)長功能,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-12-12
C++實(shí)現(xiàn)拼圖游戲代碼(graphics圖形庫)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)拼圖游戲代碼,帶有g(shù)raphics圖形庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
基于QT的TCP通信服務(wù)的實(shí)現(xiàn)
在項(xiàng)目開發(fā)過程中,很多地方都會用到TCP通信,本文主要介紹了基于QT的TCP通信服務(wù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
C語言實(shí)現(xiàn)大數(shù)值金額大寫轉(zhuǎn)換的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C語言實(shí)現(xiàn)大數(shù)值金額大寫轉(zhuǎn)換的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-03-03

