C++笔记_序列化与反序列化


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>等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值