大家好,又见面了,我是你们的朋友全栈君。
序列化
序列化
1、背景
- 1、在TCP的连接上,它传输数据的基本形式就是 二进制流 ,也就是一段一段的1和0。
-
2、在一般编程语言或者网络框架提供的API中,传输数据的基本形式是
字节
,也就是Byte。一个字节就是8个二进制位,8个Bit。
- 二进制流和字节流本质上是一样的。对于我们编写的程序来说,它需要通过网络传输的数据是结构化的数据,比如,一条命令、一段文本或者一条消息。对应代码中,这些结构化的数据都可以用一个类或者一个结构体来表示。
- 序列化的用途除了用于在网络上传输数据以外,
-
将结构化数据保存在文件中(将对象存储于硬盘上),因为文件内保存数据的形式也是二进制序列。
在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢?
- 内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用,指针,并不是直接数据块。内存中的对象数据应该具有语言独特性,例如表达相同业务的User对象(id/name/age字段),Java和PHP在内存中的数据格式应该不一样的,如果直接用内存中的数据,可能会造成语言不通。只要对序列化的数据格式进行了协商,任何2个语言直接都可以进行序列化传输、接收。
- 一个数据结构,里面存储的数据是经过非常多其他数据通过非常复杂的算法生成的,因为数据量非常大,因此生成该数据结构所用数据的时间可能要非常久,生成该数据结构后又要用作其他的计算,那么你在调试阶段,每次执行个程序,就光生成数据结构就要花上这么长的时间。假设你确定生成数据结构的算法不会变或不常变,那么就能够通过序列化技术生成数据结构数据存储到磁盘上,下次又一次执行程序时仅仅须要从磁盘上读取该对象数据就可以,所花费时间也就读一个文件的时间。
- 虽然都是二进制的数据,但是序列化的二进制数据是通过一定的协议将数据字段进行拼接。第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言。第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来。
2、定义
要想使用网络框架的API来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化。
-
简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它依据流重构对象。这两个过程结合起来,能够轻松地存储和数据传输。
- 比如,能够序列化一个对象,然后使用HTTP 通过 Internet 在client和server之间传输该对象。
3、序列化评价指标
-
1、可读性
- 序列化后的数据最好是易于人类阅读的
-
2、实现复杂度
- 实现的复杂度是否足够低
-
3、性能
- 序列化和反序列化的速度越快越好
-
4、信息密度
- 序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好
03 | 08 7a 68 61 6e 67 73 61 6e | 17 | 01 User | z h a n g s a n | 23 | true
- 1.首先我们需要标识一下这个对象的类型,这里面我们用一个字节来表示类型,比如用 03 表示这是一个 User 类型的对象。
- 2.我们约定,按照 name、age、married 这个固定顺序来序列化这三个属性。按照顺序,第一个字段是 name,我们不存字段名,直接存字段值“zhangsan”就可以了,由于名字的长度不固定,我们用第一个字节 08 表示这个名字的长度是 8 个字节,后面的 8 个字节就是 zhangsan。
- 3.第二个字段是年龄,我们直接用一个字节表示就可以了,23 的 16 进制是 17 。
- 4.最后一个字段是婚姻状态,我们用一个字节来表示,01 表示已婚,00 表示未婚,这里面保存一个 01。
同样的一个User对象,JSON序列化后
({"name":"zhangsan","age":"23","married":"true"})
- JSON序列化后需要47个字节,专用的序列化方法只要12个字节就够了。
- 专用的序列化方法显然更高效,序列化出来的字节更少,在网络传输过程中的速度也更快。但缺点是,需要为每种对象类型定义专门的序列化和反序列化方法,实现起来太复杂了,大部分情况下是不划算的。
4、序列化实例
案例1:
//Srlz1.cpp: 将一个类的一个对象序列化到文件 #include <iostream> #include <fcntl.h> #include <vector> #include <stdio.h> using namespace std; //定义类CA //数据成员:int x; //成员函数:Serialize:进行序列化函数 // Deserialize反序列化函数 // Show:数据成员输出函数 class CA private: int x; //定义一个类的数据成员。 public: CA() //默认构造函数 x = ; CA(int y):x(y) //定义构造函数,用初始化列表初始化数据成员 virtual ~CA() //析构函数 public: //序列化函数:Serialize //成功,返回0,失败,返回0; int Serialize(const char* pFilePath) const int isSrlzed = -; FILE* fp; //define a file pointer //以读写方式打开文件,并判断是否打开; if ((fp = fopen(pFilePath, "w+")) == NULL) printf("file opened failure\n"); return -; //若打开失败,则返回-1; //调用fwrite函数,将对象写入文件; isSrlzed = fwrite(&x, sizeof(int), , fp); //判断写入是否成功; if ((- == isSrlzed) || ( == isSrlzed)) printf("Serialize failure\n"); return -; //若写入失败,则返回-1; if(fclose(fp) != ) //关闭文件 printf("Serialize file closed failure.\n"); return -; printf("Serialize succeed.\n"); return ; //序列化成功,返回0; } //反序列化函数: //成功,返回0,失败,返回-1; int Deserialize(const char* pFilePath) int isDsrlzed = -; FILE* fp; //以读写方式打开文件,并判断是否打开; if ((fp = fopen(pFilePath, "r+")) == NULL) printf("file opened failure.\n"); return -; //调用fread函数,读取文件中的对象 ; isDsrlzed = fread(&x, sizeof(int), , fp); //判断是否成功读入 if ((- == isDsrlzed)||( == isDsrlzed)) printf("Deserialize failure.\n"); return -; //若读取失败,则返回-1; if(fclose(fp) != ) printf("Deserialize file closed failure.\n"); return -; printf("Deserialize succeed.\n"); return ; //反序列化成功,则返回0; } //成员对象输出显示函数 void Show() cout<< "in Show():"<< x << endl; };
int main(int argc, char const *argv[]) CA as(); //定义一个类对象,并初始化; //调用序列化函数,将对象as序列化到文件data.txt中; as.Serialize("data.txt"); CA ad; //定义一个类对象,用来记录反序列化对象 //调用反序列化函数,将文件data.txt中的对象反序列化到ad对象; ad.Deserialize("data.txt"); ad.Show(); //调用输出显示函数; return ; }
更详细的案例 ,简单注释: 1、CharVec.h : 定义一个 vector < char> 类型字节数组,也就是一个定义的容器,为下面的 DataStream 中存放数据提供接口:
#ifndef CHARVEC_H #define CHARVEC_H #include <memory> class CharVec{ public: CharVec(); CharVec(const CharVec &vec); CharVec &operator =(const CharVec &vec); ~CharVec(); bool operator ==(const CharVec &vec) const; size_t size() const;//vector里实际存放数据的大小 size_t capacity() const;//capacity是vector的内存分配大小 char *begin() const; char *end() const; void push(const char *data, int len); void push(const std::string &str); void push(char c); void removeFromFront(int len); void clear(); private: void checkAndAlloc(); void reallocate(); void free(); std::pair<char *, char *> allocAndCopy(char *begin, char *end); private: char *m_Elements; // 首元素 char *m_FirstFree; // 最后一个实际元素之后的位置 char *m_Cap; // 分配内存末尾之后的位置 std::allocator<char> m_Allocator; // 内存分配器 #endif // CHARVEC_H
2、CharVec.cpp :对CharVec.h 声明的函数进行定义
// CharVec.cpp #include "CharVec.h" CharVec::CharVec() :m_Elements(nullptr), m_FirstFree(nullptr),m_Cap(nullptr) }//构造函数 CharVec::CharVec(const CharVec &vec)//拷贝构造 auto newData = allocAndCopy(vec.begin(), vec.end());//allocAndCopy分配空间,并且初始化 m_Elements = newData.first; m_FirstFree = newData.second; m_Cap = newData.second; CharVec &CharVec::operator =(const CharVec &vec)//=重载 auto newData = allocAndCopy(vec.begin(), vec.end()); free(); m_Elements = newData.first; m_FirstFree = newData.second; m_Cap = newData.second; return *this; CharVec::~CharVec()//析构 free(); bool CharVec::operator ==(const CharVec &vec) const//==重载 if (m_Elements == vec.m_Elements && m_FirstFree == vec.m_FirstFree && m_Cap == vec.m_Cap) { return true; return false; size_t CharVec::size() const//当前元素数目 return m_FirstFree - m_Elements; size_t CharVec::capacity() const//容器总的空间大小 return m_Cap - m_Elements; char *CharVec::begin() const return m_Elements; char *CharVec::end() const return m_FirstFree; void CharVec::push(const char *data, int len) if (len <= 0) { return ; for (int i = 0; i < len; ++i) { push(data[i]); void CharVec::push(const std::string &str) push(str.c_str(), str.size()); void CharVec::push(char c) checkAndAlloc(); m_Allocator.construct(m_FirstFree++, c); void CharVec::removeFromFront(int len)//从m_Element开始释放掉len长度的数据。 if (len > size()) { return ; char *from = m_Elements; char *to = m_Elements + len; m_Elements += len; for (int i = 0; i < len; ++i) { m_Allocator.destroy(--to); m_Allocator.deallocate(from, m_Elements - from); void CharVec::clear()//容器清空操作 free(); m_Elements = nullptr; m_FirstFree = nullptr; m_Cap = nullptr; //checkAndAlloc()会先判断size是不是和capacity相等, //然后调用reallocate进行内存的分配,重新分配的空间是原来的2倍, //然后数据转移,使用std::move而不是拷贝可以提高效率。 void CharVec::checkAndAlloc() if (size() == capacity()) { reallocate(); void CharVec::reallocate()//类似vector的扩容操作 auto newCapacity = size() ? 2 * size() : 1;//重新分配的空间是原来的2倍 auto newData = m_Allocator.allocate(newCapacity);//allocate分配空间 auto dest = newData; auto ele = m_Elements; for (size_t i = 0; i != size(); ++i) { m_Allocator.construct(dest++, std::move(*ele++));//construct初始构造 free(); m_Elements = newData; m_FirstFree = dest; m_Cap = m_Elements + newCapacity; void CharVec::free() if (m_Elements) { for (auto p = m_FirstFree; p != m_Elements;) { m_Allocator.destroy(--p);//destroy析构对象,此时空间还是可以使用 m_Allocator.deallocate(m_Elements, m_Cap - m_Elements);//deallocate回收空间 std::pair<char *, char *> CharVec::allocAndCopy(char *begin, char *end) auto startPos = m_Allocator.allocate(end - begin); return { startPos, std::uninitialized_copy(begin, end, startPos)}; }
3、DataHeader类的声明:定义id,及headerlen,totalLen相关客户属性
// DataHeader.h #ifndef DATAHEADER_H #define DATAHEADER_H struct DataHeader DataHeader(int id = 0); bool operator==(const DataHeader &header); void reset(); const static int s_HeaderLen = 3 * sizeof(int); int m_Id; int m_HeaderLen; int m_TotalLen; #endif // DATAHEADER_H
3、DataStream.h:
-
支持序列化和反序列化操作,
- 枚举继承char类型意思是说这个枚举里的枚举值底层是用char来存储的
- 支持是序列化相关的数据类型,
- 往这个类的写数据和读数据都是提供了两种形式,
- 流式操作符(<< 或者 >> )和函数(readVal,writeVal)
// DataStream.h #ifndef DATASTREAM_H #define DATASTREAM_H #include <memory> #include <map> #include <list> #include <vector> #include <set> #include "DataHeader.h" #include "CharVec.h" class CustomTypeInterface;//前向声明 class DataStream public: DataStream(std::unique_ptr<DataHeader> *header = nullptr); DataStream(const DataStream &stream); DataStream& operator =(const DataStream &stream); enum class DataType : char { UnKnown, Boolean, Char, WChar, UInt, Int64, Double, String, WString, Vector, List, CustomType, bool operator == (const DataStream &stream) const; // 指数组里存放的数据 int totalSize() const { return m_Header->m_TotalLen; }//数据的总长 int headerSize() const { return m_Header->m_HeaderLen; }//头部的长度 int dataSize() const { return m_Header->m_TotalLen - m_Header->m_HeaderLen;}//内容数据的长度 void clear(); // write void writeHeader(); void writeData(const char *data, int len); //这里是写入不同数据类型的数据 DataStream& operator<<(char val); void writeVal(char val); DataStream& operator<<(wchar_t val); void writeVal(wchar_t val); DataStream& operator <<(bool val); void writeVal(bool val); DataStream& operator <<(int val); void writeVal(int val); DataStream& operator <<(unsigned int val); void writeVal(unsigned int val); DataStream& operator <<(int64_t val); void writeVal(int64_t val); DataStream& operator <<(double val); void writeVal(double val); DataStream& operator <<(const std::string &val); void writeVal(const std::string &val); DataStream& operator <<(const std::wstring &val); void writeVal(const std::wstring &val); DataStream& operator <<(CustomTypeInterface *val); void writeVal(CustomTypeInterface *val); //这里是往不同的STL容器中写入模板类型的数据 template<typename T> DataStream& operator <<(const std::vector<T>& val); template<typename T> void writeVal(const std::vector<T>& val); template<typename T> DataStream& operator <<(const std::list<T>& val); template<typename T> void writeVal(const std::list<T>& val); template<typename T1, typename T2> DataStream& operator <<(const std::map<T1, T2>& val); template<typename T1, typename T2> void writeVal(const std::map<T1, T2>& val); template<typename T> DataStream& operator <<(const std::set<T>& val); template<typename T> void writeVal(const std::set<T>& val); // read void readHeader(const char *data); template<typename T> bool readData(T *val); bool operator>>(char &val); bool readVal(char &val); bool operator>>(wchar_t& val); bool readVal(wchar_t &val); bool operator>>(bool &val); bool readVal(bool &val); bool operator>>(int &val); bool readVal(int &val); bool operator>>(unsigned int &val); bool readVal(unsigned int &val); bool operator>>(int64_t &val); bool readVal(int64_t &val); bool operator>>(double &val); bool readVal(double &val); bool operator>>(std::string &val); bool readVal(std::string &val); bool operator>>(std::wstring &val); bool readVal(std::wstring &val); bool operator>>(CustomTypeInterface *val); bool readVal(CustomTypeInterface *val); template<typename T> bool operator>>(std::vector<T> &val); template<typename T> bool readVal(std::vector<T> &val); template<typename T> bool operator>>(std::list<T> &val); template<typename T> bool readVal(std::list<T> &val); template<typename T1, typename T2> bool operator>>(std::map<T1, T2> &val); template<typename T1, typename T2> bool readVal(std::map<T1, T2> &val); template<typename T> bool operator>>(std::set<T> &val); template<typename T> bool readVal(std::set<T> &val); // Serialize and Deserialize int Serialize(char *buf) const; bool Deserialize(const char *buf, int len); private: std::unique_ptr<DataHeader> m_Header;//存储的客户类型指针 CharVec m_DataBuffer;//存储的容器 int m_IsFirstWrite;//判断是否为第一次写入 };
4、DataStream.cpp:DataStream.h文件相关函数的实现
#include "DataStream.h" #include "CustomTypeInterface.h" DataStream::DataStream(std::unique_ptr<DataHeader> *header) : m_IsFirstWrite(true)//构造函数的实现 if (header == nullptr) { //header对象为空指针,重置新的对象指针 m_Header.reset(new DataHeader); else { m_Header.reset(header->release());//release()释放关联的原始指针,unique_ptr相关的函数 DataStream::DataStream(const DataStream &stream)//拷贝构造 operator =(stream); DataStream &DataStream::operator =(const DataStream &stream)//=重载 if (&stream == this) { //比较对象和原对象相同,没有赋值的必要了 return *this; m_Header.reset(new DataHeader);//重载并且初始化 *m_Header = *stream.m_Header;//相关赋值操作 m_DataBuffer = stream.m_DataBuffer; m_IsFirstWrite = stream.m_IsFirstWrite; return *this; bool DataStream::operator ==(const DataStream &stream) const//==重载 if (&stream == this) { return true; if (m_Header.get() == stream.m_Header.get() && m_DataBuffer == stream.m_DataBuffer) { return true; return false; void DataStream::clear() m_IsFirstWrite = true; m_DataBuffer.clear(); m_Header->reset(); void DataStream::writeHeader() int headerLen = DataHeader::s_HeaderLen; writeData((char *)&(m_Header->m_TotalLen), sizeof(int)); writeData((char *)&headerLen, sizeof(int)); writeData((char *)&m_Header->m_Id, sizeof(int)); m_Header->m_HeaderLen = headerLen; void DataStream::writeData(const char *data, int len) if (len == 0) { return ; //把他的type写入, //如果是第一写入,先把header写入, 然后再写数据,更新totalLen if (m_IsFirstWrite) { m_IsFirstWrite = false; writeHeader(); //然后在把数据写入 m_DataBuffer.push(data, len); m_Header->m_TotalLen += len;//更新totalLen memcpy(m_DataBuffer.begin(), &m_Header->m_TotalLen, sizeof(int)); void DataStream::writeVal(char val) char type = (char)DataType::Char; writeData((char *)&(type), sizeof(char)); writeData(&val, sizeof(char)); void DataStream::writeVal(const std::string &val) char type = (char)DataType::String; writeData((char *)&(type), sizeof(char)); int size = val.size(); writeVal(size); writeData(val.c_str(), size); void DataStream::writeVal(CustomTypeInterface *val) val->serialize(*this, (char)DataType::CustomType); void DataStream::readHeader(const char *data) int *p = (int *)data; m_Header->m_TotalLen = *p++; m_Header->m_HeaderLen = *p++; m_Header->m_Id = *p++; m_Header->m_TotalLen -= m_Header->m_HeaderLen; m_Header->m_HeaderLen = 0; //从dataBuffer的数据取出来,然后更新totalLen. //由于这个函数是模板函数,所以我们把他放在了头文件。 /*template<typename T> bool DataStream::readData(T *val) { int size = m_DataBuffer.size(); int count = sizeof(T); if (size < count) { return false; } *val = *((T*)m_DataBuffer.begin()); m_DataBuffer.removeFromFront(count); m_Header->m_TotalLen -= count; return true; }*/ //先读取出来类型,然后读取数据 bool DataStream::readVal(char &val) char type = 0; if (readData(&type) && type == (char)DataType::Char) { return readData(&val); return false; bool DataStream::readVal(std::string &val) char type = 0; if (readData(&type) && type == (char)DataType::String) { int len = 0; if (readVal(len) && len > 0) { val.assign(m_DataBuffer.begin(), len); m_DataBuffer.removeFromFront(len); m_Header->m_TotalLen -= len; return true; return false; bool DataStream::readVal(CustomTypeInterface *val) return val->deserialize(*this, (char)DataType::CustomType); int DataStream::Serialize(char *buf) const//序列化 int totalLen = m_Header->m_TotalLen; int size = m_DataBuffer.size(); if (size <= 0 || totalLen == 0 || size != totalLen) { return 0; memcpy(buf, m_DataBuffer.begin(), totalLen); return totalLen; bool DataStream::Deserialize(const char *buf, int len)//反序列化 if (buf == nullptr || len <= 0) { return false; readHeader(buf); m_DataBuffer.clear(); m_DataBuffer.push(buf + DataHeader::s_HeaderLen, len - DataHeader::s_HeaderLen); return true; }
5、CustomTypeInterface:自定义类型,比如你自己定义了一个结构体,怎样传输它呢。我们为自定义的结构体定义一个接口类。
class CustomTypeInterface public: virtual ~CustomTypeInterface() = default; virtual void serialize(DataStream &stream, char type) const = 0; virtual bool deserialize(DataStream &stream, char type) = 0; };
测试
#include <iostream> #include "DataStream.h" #include "CustomTypeInterface.h" class Test : public CustomTypeInterface public: SerializeAndDeserialize(Test, m_A * m_B); public: int m_A; bool m_B; int main(int argc, char *argv[]) char c1 = 'c'; Test t; t.m_A = 1; t.m_B = false; DataStream stream; stream.writeVal(c1); stream.writeVal(&t); int size = stream.totalSize(); char *data = new char[size]; stream.Serialize(data); DataStream stream2; stream2.Deserialize(data, size); char c2; Test t2; stream2.readVal(c2); stream2.readVal(&t2); std::cout << c2 << t2.m_A << t2.m_B;