序列化与反序列化 StreamBuffer 类
1、概述作用
在网络数据传输与数据存储中,经常涉及到C++数据到二进制数据的转换。这里设计一个StreamBuffer
类,使用操作符<<
及>>
完成C++数据与二进制数据的转换。
2、使用示例
自定义一个结构体Mydata
,包含int、string、float和枚举成员,定义接口marshal
将成员数据序列化成二进制数据,定义unmarshal
接口,将二进制数反序列化成成员数据。
#include "StreamBuffer.hpp"
#include <iostream>
enum class EM_TEST :int16_t
{
TYPE_1,
TYPE_2,
TYPE_3
};
struct Mydata// :public mmrUtil::IDealByStream<std::string>
{
Mydata() = default;
Mydata(std::string str1, int n, float f, std::string str2, EM_TEST emTest)
: strValue1(std::move(str1))
, nValue(n)
, fValue(f)
, strValue2(std::move(str2))
, emTest(emTest)
{
}
std::string strValue1;
int nValue;
float fValue;
std::string strValue2;
EM_TEST emTest;//枚举值
void marshal(mmrUtil::StreamBuffer& dataStream) const
{
dataStream << strValue1;
dataStream << nValue;
dataStream << fValue;
dataStream << strValue2;
dataStream << emTest;
}
void unmarshal(mmrUtil::StreamBuffer& dataStream)
{
//通常需要对数据做一些校验,并且保证反序列化数据与序列化书序严格一致
dataStream >> strValue1;
dataStream >> nValue;
dataStream >> fValue;
dataStream >> strValue2;
dataStream >> emTest;
}
};
int main()
{ //复制数据到byte
char transData[1024] = { 0 };
int byteLen = 0;
//数据序列化二进制保存
{
mmrUtil::StreamBuffer dataMarshal;
Mydata dataTest = { "你好",5,2.5,"hello" ,EM_TEST::TYPE_2 };
std::cout << "origin data is " << dataTest.strValue1 << " " << dataTest.nValue << " " << dataTest.fValue << " " << dataTest.strValue2 << " " << static_cast<int>(static_cast<int16_t>(dataTest.emTest)) << std::endl;
dataTest.marshal(dataMarshal);
byteLen = dataMarshal.dataSize();
memcpy(transData, dataMarshal.getStartPtr(), byteLen);
}
//反序列化
{
//使用vector<char类型>
mmrUtil::StreamBuffer dataUnmarshal(transData, byteLen);
Mydata dataTrans;
dataTrans.unmarshal(dataUnmarshal);
std::cout << "unmarshal data is "
<< dataTrans.strValue1 << " "
<< dataTrans.nValue << " "
<< dataTrans.fValue << " "
<< dataTrans.strValue2 << " "
<< static_cast<int16_t>(dataTrans.emTest)
<< std::endl;
}
std::cout << "输入任意字符继续..." << std::endl;
std::cin.get();
return 0;
}
3、StreamBuffer 类源码及说明
/**
* @file SteramBuffer.h
* @brief
* @author Mounmory (237628106@qq.com) https://github.com/Mounmory
* @date
*
*
*/
#ifndef MMR_UTIL_STREAM_BUGGER_H
#define MMR_UTIL_STREAM_BUGGER_H
#include <string>
#include <memory>
#include <cstring>
#include <type_traits>
#include <functional>
#include <cassert>
/*
可以处理数据流的buffer
*/
enum class emEndian //计算机大小端
{
LITTLE = 1,
BIG = 0
};
namespace mmrUtil
{
class StreamBuffer
{
using BYTE = uint8_t;
public:
StreamBuffer(emEndian streamEndian = emEndian::LITTLE)
: m_endianStream(streamEndian)
{
initMachineEndian();
}
/*
这个构造函数用于直接使用外部地址数据解析,仅限使用operator >> 将数据赋值给其他数据,指针生命周期由外部控制,确保在整个生命周期指针的有效性。
即使调用其它接口也是内存安全的,若涉及导致重新分配内存,将拷贝地址上的数据然后丢弃这个地址
*/
StreamBuffer(void* buffer, uint32_t length, emEndian streamEndian = emEndian::LITTLE)
: m_endianStream(streamEndian)
, m_ptrStart((BYTE*)buffer)//只赋值指针和写位置
, m_ulWritePos(length)
{
initMachineEndian();
}
StreamBuffer(const StreamBuffer&) = delete;
StreamBuffer(StreamBuffer&& rhs)
: m_endianStream(rhs.m_endianStream)
, m_endianLocal(rhs.m_endianLocal)
, m_ulReadPos(std::exchange(rhs.m_ulReadPos, 0))
, m_ulWritePos(std::exchange(rhs.m_ulWritePos, 0))
, m_ptrStart(std::exchange(rhs.m_ptrStart, nullptr))
, m_ulCapacity(std::exchange(rhs.m_ulCapacity, 0))
, m_ptrBuf(std::exchange(rhs.m_ptrBuf, nullptr)) { }
//拷贝赋值函数
StreamBuffer& operator = (const StreamBuffer&) = delete;
//移动赋值函数
StreamBuffer& operator = (StreamBuffer&& rhs)
{
if (this != &rhs)
{
m_ulReadPos = std::exchange(rhs.m_ulReadPos, 0);
m_ulWritePos = std::exchange(rhs.m_ulWritePos, 0);
m_ptrStart = std::exchange(rhs.m_ptrStart, nullptr);
m_ulCapacity = std::exchange(rhs.m_ulCapacity, 0);
m_ptrBuf = std::exchange(rhs.m_ptrBuf, nullptr);
}
return *this;
}
//模板函数,将基本数字数据类型序列化
template<typename _Ty>
typename std::enable_if<std::is_arithmetic<_Ty>::value && std::is_same<std::decay_t<_Ty>, _Ty>::value, StreamBuffer&>::type
operator << (_Ty data)
{
static constexpr uint32_t dataSize = sizeof(_Ty);
BYTE* ptrData = reinterpret_cast<BYTE*>(&data);
doFlip(ptrData, dataSize);
writeBuf(ptrData, dataSize);
return *this;
}
//模板函数,自定义枚举数据类型序列化
template<typename _Ty>
typename std::enable_if<std::is_enum<_Ty>::value && std::is_same<std::decay_t<_Ty>, _Ty>::value, StreamBuffer&>::type
operator << (_Ty data)
{
using Type = std::underlying_type_t<_Ty>;
(*this) << static_cast<Type>(data);
return *this;
}
//模板函数,对基本数字数据类型反序列化
template<typename _Ty>
typename std::enable_if<std::is_arithmetic<_Ty>::value && std::is_same<std::decay_t<_Ty>, _Ty>::value, StreamBuffer&>::type
operator >> (_Ty& data)
{
static constexpr uint32_t dataSize = sizeof(_Ty);
BYTE* ptrData = reinterpret_cast<BYTE*>(&data);
readBuf(ptrData, dataSize);
doFlip(ptrData, dataSize);
return *this;
}
//模板函数,对枚举数据类型反序列化
template<typename _Ty>
typename std::enable_if<std::is_enum<_Ty>::value && std::is_same<std::decay_t<_Ty>, _Ty>::value, StreamBuffer&>::type
operator >> (_Ty& data)
{
using Type = std::underlying_type_t<_Ty>;
(*this) >> (*reinterpret_cast<Type*>(&data));
return *this;
}
//对string类型序列化
StreamBuffer& operator << (const std::string& strData)
{
uint32_t ulSize = strData.size();
(*this) << ulSize;
writeBuf(strData.data(), ulSize);
return *this;
}
//对string类型反序列化
StreamBuffer& operator >> (std::string& strData)
{
uint32_t ulSize;
(*this) >> ulSize;
assert(m_ulWritePos >= m_ulReadPos + ulSize);
strData.resize(ulSize);
readBuf(const_cast<char*>(strData.data()), ulSize);
return *this;
}
//设置内存空间大小
void setCapacity(uint32_t length)
{
if (length > m_ulCapacity)
{
m_ulCapacity = length;
auto ptrNe = std::unique_ptr<BYTE, std::function<void(BYTE*)>>(new BYTE[m_ulCapacity], [](BYTE* ptr) {delete[] ptr; });
memcpy(ptrNe.get(), m_ptrStart, m_ulWritePos);
m_ptrBuf = std::move(ptrNe);
m_ptrStart = m_ptrBuf.get();
}
}
//获取当前写位置,并在外部进行内存写入
BYTE* getWritePosAndWrite(uint32_t length)
{
checkBufCapacity(m_ulWritePos + length);
m_ulWritePos += length;
return (m_ptrStart + m_ulWritePos - length);
}
//获取当前读位置,并在外部读取数据
BYTE* getReadPosAndRead(uint32_t length)
{
m_ulReadPos += length;
assert(m_ulWritePos >= m_ulReadPos);
return (m_ptrStart + m_ulReadPos - length);
}
//如果数据流大小端与本地大小端不一致,进行字节序翻转
void doFlip(BYTE* buffer, uint32_t dataSize)
{
if (m_endianStream != m_endianLocal && dataSize >= 2)
{
BYTE* start = buffer;
BYTE* end = buffer + (dataSize - 1);
while (start < end)
{
std::swap(*start, *end);
++start;
--end;
}
}
}
//清空数据(读写位置)
void clear()
{
m_ulReadPos = 0;
m_ulWritePos = 0;
}
//内部数据大小
uint32_t dataSize() { return (m_ulWritePos - m_ulReadPos); }
//获取内存空间大小
uint32_t getCapacity()const { return m_ulCapacity; }
//内存数据起始位置
BYTE* getStartPtr()const { return m_ptrStart; }
//从StreamBuffer读取数据位置
uint32_t getReadPos()const { return m_ulReadPos; }
//向StreamBffer写数据位置
uint32_t getWritePos()const { return m_ulWritePos; }
private:
//将数据写到StreamBuffer
void writeBuf(const void* buffer, uint32_t length)
{
checkBufCapacity(m_ulWritePos + length);
memcpy(m_ptrStart + m_ulWritePos, buffer, length);
m_ulWritePos += length;
}
//从StreamBuffer读数据
void readBuf(void* buffer, uint32_t length)
{
memcpy(buffer, m_ptrStart + m_ulReadPos, length);
m_ulReadPos += length;
assert(m_ulWritePos >= m_ulReadPos);
}
//检查内存空间是否够用
void checkBufCapacity(uint32_t length)
{
if (length > m_ulCapacity)
{
setCapacity(2 * length);
}
}
//初始化本地大小端
void initMachineEndian()//初始化本地大小端Endian
{
long one(1);
char e = (reinterpret_cast<char*>(&one))[0];
(e == (char)1) ? m_endianLocal = emEndian::LITTLE : m_endianLocal = emEndian::BIG;
}
private:
const emEndian m_endianStream;//数据流的的大小端
emEndian m_endianLocal;//本地计算机大小端
//内部数据处理相关
uint32_t m_ulReadPos = 0;//buf读位置
uint32_t m_ulWritePos = 0;//buf写位置
BYTE* m_ptrStart = nullptr;//内部数据起始地址
uint32_t m_ulCapacity = 0;//buf总空间大小
std::unique_ptr<BYTE, std::function<void(BYTE*)>> m_ptrBuf = nullptr;//内部数据分配的内存地址指针
};
}
#endif
4、特点分析
相比JSON/XML等通用格式,具有以下特点:
- 转换速度更快
- 支持类型更丰富
- 内存占用更小
- 非标准化方式,将二进制数据转换为C++数据时,要做好校验(类似ProtoBuf)
5、扩展
可以通过修改代码实现对一些C++标准容器的支持,如std::vector<int32_t>等