天天看點

C++ 序列化:從設計到實作

github連結:

https://github.com/PengChaoJay/CPP/tree/main/Serialization

概念

序列化 (serialization) 是将對象的狀态資訊轉化為可以存儲或傳輸的形式的過程。在序列化期間,對象将其目前狀态寫入臨時或持久性存儲區。以後可以通過從存儲區中讀取或反序列化對象的狀态,重新建立該對象。

序列化的方式

  1. 文本格式:JSON,XML
  2. 二進制格式:protobuf

二進制序列化

序列化: 将資料結構或對象轉換成二進制串的過程

反序列化:經在序列化過程中所産生的二進制串轉換成資料結構或對象的過程

- 序列化後,資料小,傳輸速度快 - 序列化、反序列化速度快

示範

1. 基本類型序列化、反序列化

基本類型序列化

int main()
{
    DataStream ds;
    int n =123;
    double d = 23.2;
    string s = "hellow serialization";
    ds << n <<d <<s;
    ds.save("a.out");
}
           

基本類型的反序列化

{
    DataStream ds;
    int n;
    double d;
    string s;
    ds.load("a.load");
    ds<<d<<s<<d;
    std::cout<<n<<d<<s<<std::endl;
}
           

2.複合類型資料序列化、反序列化

複合類型資料序列化

int main()
{
    std::vector<int>v{3,2,1};
    std::map<string,string>m;
    m["name"] = "kitty";
    m["phone"] = "12121";
    m["gender"] = "male";
    DataStream ds;
    ds<<v<<s;
    ds.save("a.out");
}
複合類型資料反序列化
``` C++
int main()
{
    DataStreawm ds;
    ds.load("a.out");
    std::vector<int>v;
    std::map<string,string>m;
    ds>>v>>m;
    for(auto it = v.begin();it != v.end();it++)
    {
        std::cout<<*it<<std::endl;
    }
    for(auto it = m.begin();it!= m.end;it++)
    {
        std::cout<<it->first<<"="<<it->second<<std::endl;
    }
}
           

3.自定義類的序列化、反序列化

自定義類的序列化

class A:public Serialization
{
public:
    A();
    ~A();
    A(const string & name,int age):m_name(name),m_age(age){}
    void show()
    {
        std::cout<<m_name<<" "<<m_age<<std::endl;
    }
    //需要序列化的字段
    SERIALIZE(m_name,m_age);
private:
    string m_name;
    int m_age;
}
int main()
{
    A a("Hell",12);
    DataStream ds;
    ds<<a;
    ds.save("a.out");
}
           

反序列化類的類型

int main()
{
    DataStreawm ds;
    ds.load("a.out");
    std::vector<int>v;
    std::map<string,string>m;
    ds>>v>>m;
    for(auto it = v.begin();it != v.end();it++)
    {
        std::cout<<*it<<std::endl;
    }
    for(auto it = m.begin();it!= m.end;it++)
    {
        std::cout<<it->first<<"="<<it->second<<std::endl;
    }
}
           

3.自定義類的序列化、反序列化

自定義類的反序列化

class A:public Serialization
{
public:
    A();
    ~A();
    A(const string & name,int age):m_name(name),m_age(age){}
    void show()
    {
        std::cout<<m_name<<" "<<m_age<<std::endl;
    }
    //需要序列化的字段
    SERIALIZE(m_name,m_age);
private:
    string m_name;
    int m_age;
}
class B:public Serialization
{
public:
    B();
    ~B();
    void add(const A & a)
    {
        m_vector.add(a);
    }
    B(const string & name,int age):m_name(name),m_age(age){}
    void show()
    {
        for(auto it = m_vector.begin();it! = m_vector.end();it++)
        {
            it->show();
        }
    }
    //需要序列化的字段
    SERIALIZE(m_vector);
private:
    std::vector<A> m_vector;
}
int main()
{
    // 序列化
    // B b;
    // b.add(A("hello",12));
    // b.add(A("liuc",21));
    // b.add(A("wang",34));
    // DataSream ds;
    // ds<<b;
    // ds.save("a.out");
    //反序列化
    B b;
    DataSream ds;
    ds.load("a.out");
    ds>>b;
    b.show();
}
           

Protobuf 與 srialization的差別

protobuf serialize
二進制格式
資料體積
編碼速度
資料格式類型 豐富 更加豐富
消息定義檔案 需要 不需要
需要編譯 需要 不需要
代碼實作 複雜 簡單

資料類型的定義

enum DataType
{
    BOOL =0,
    CHAR,
    INT32,
    INT64,
    FLOAT,
    DOUBLE,
    STRING,
    VECTOR,
    LIST,
    MAP,
    SET,
    CUSTOM
}
           

基本類型序列化+反序列化

基本資料類型編碼

字段類型 字段長度(位元組) 底層編碼格式
bool 2 Type(1) + Value(1)
char 2 Type(1) + Value(1)
int32 5 Type(1) + Value(4)
int64 9 Type(1) + Value(8)
float 5 Type(1) + Value(4)
double 9 Type(1) + Value(8)
stirng 可變長度 Type(1) +Length(5) + Value(變成)
對于string類型,1個位元組代表類型,長度用的是int32
### 複合類型序列化+ 反序列化
#### 複合資料類型編碼
字段類型 字段長度(位元組) 底層編碼格式
:---: :----: :----:
vector 可變長 Type(1) + length(5) + Value(T+T+T+...)
list 可變長 Type(1) + length(5) + Value(T+T+T+...)
map 可變長 Type(1) + length(5) + Value((k,v)+(k,v)+(k,v)+...)
set 可變長 Type(1) + length(5) + Value(T+T+T+...)
其中length代表的int32的表示的長度
#### 自定義類型序列化+ 反序列化
#### 自定義對象類型編碼
字段類型 字段長度(位元組) 底層編碼格式
:---: :----: :----:
自定義類 可變長 Type(1) +Value(D1+D2+D3+...)

Serializable 接口類

class Serializable
{
    public:
        virtual void serializable (DataStream & stream) const =0;
        virtual bool unserializable (DataStream & stream) =0;
}
           

SERIALIZE宏(參數化實作)

#define SERIALIZE(...)                              \
    void serialize(DataStream & stream) const       \
    {                                               \
        char type = DataStream::CUSTOM;             \
        stream.write((char *)&type, sizeof(char));  \
        stream.write_args(__VA_ARGS__);             \
    }                                               \
                                                    \
    bool unserialize(DataStream & stream)           \
    {                                               \
        char type;                                  \
        stream.read(&type, sizeof(char));           \
        if (type != DataStream::CUSTOM)             \
        {                                           \
            return false;                           \
        }                                           \
        stream.read_args(__VA_ARGS__);              \
        return true;                                \
    }
           

大端與小端

位元組序列

位元組順序又稱為端序或尾序(Endianness),在計算機科學領域,指的是電腦記憶體中在數字通信鍊路中,組成多位元組的字的位元組排列順序。

小端

little-Endian:将低序位元組存儲在起始位址(在低位編位址),在變量指針轉換過程中位址儲存不變,比如,int64 轉到 int32,對于機器計算來說更友好和自然

大端

Big-Endian:将高序位元組存儲在起始位址(高位編制),記憶體順序和數字的書寫順序是一緻的,對于人的直覺思維比較容易了解,網絡位元組序統一采用Big-Endian

檢測位元組序

  1. 使用庫函數
#include <endian.h>
__BYTE_ORDER == __LITTLE_ENDIAN

__BYTE_ORDER == __BIG_ENDIAN
           
  1. 通過位元組存儲位址判斷
#include <stdio.h>
#include<string.h>
int main()
{
    int n = 0x12345678;
    char str[4];
    memcpy(str,&n,sizeof(int));
    for(int i = 0;i<sizeof(int);i++)
    {
        printf("%x\n",str[i]);
    }
    if(str[0]==0x12)
    {
        printf("BIG");
    }else if (str[0] == 0x78){
        printf("Litte");
    }else{
        printf("unknow");
    }
}