16.网络通信-通信必备知识-序列化和反序列化2进制数据-反序列化
16.1 知识点
字节数组转非字符串类型
- 关键类:BitConverter
- 所在命名空间:System
- 主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] byteArray1 = BitConverter.GetBytes(99);
int i = BitConverter.ToInt32(byteArray1, 0); // 第二个参数传从字节数组第几个字节开始转换
print(i); // 99
字节数组转字符串类型
- 关键类:Encoding
- 所在命名空间:System.Text
- 主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型
byte[] byteArray2 = Encoding.UTF8.GetBytes("人间自有韬哥在");
string str = Encoding.UTF8.GetString(byteArray2, 0, byteArray2.Length); // 第二个参数传从字节数组第几个字节开始转换 第三个参数是长度
print(str); // 人间自有韬哥在
如何将二进制数据转为一个类对象
获取到对应的字节数组
PlayerInfo playerInfo1 = new PlayerInfo();
playerInfo1.lev = 10;
playerInfo1.name = "韬老狮";
playerInfo1.atk = 88;
playerInfo1.sex = false;
byte[] playerInfoBytes1 = playerInfo1.GetBytes(); // 使用上次封装好得到字节数组的方法得到字节数组
将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
PlayerInfo playerInfo2 = new PlayerInfo();
// 等级
int index = 0;
playerInfo2.lev = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4; // 索引加上对应字节
print(playerInfo2.lev); // 10
// 姓名的长度
int length = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4; // 索引加上对应字节
// 姓名字符串
playerInfo2.name = Encoding.UTF8.GetString(playerInfoBytes1, index, length);
index += length; // 索引加上对应字符串长度
print(playerInfo2.name); // 韬老狮
// 攻击力
playerInfo2.atk = BitConverter.ToInt16(playerInfoBytes1, index);
index += 2; // 索引加上对应字节
print(playerInfo2.atk); // 88
// 性别
playerInfo2.sex = BitConverter.ToBoolean(playerInfoBytes1, index);
index += 1; // 索引加上对应字节
print(playerInfo2.sex); // false
总结
我们对类对象的2进制反序列化主要用到的知识点是
- BitConverter转换字节数组为非字符串的类型的变量
- Encoding.UTF8转换字节数组为字符串类型的变量(注意:先读长度,再读字符串)
转换流程是
- 获取到对应的字节数组
- 将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
16.2 知识点代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using UnityEngine;
public class Lesson16_网络通信_通信必备知识_序列化和反序列化2进制数据_反序列化 : MonoBehaviour
{
void Start()
{
#region 知识点一 字节数组转非字符串类型
//关键类:BitConverter
//所在命名空间:System
//主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] byteArray1 = BitConverter.GetBytes(99);
int i = BitConverter.ToInt32(byteArray1, 0);//第二个参数传从字节数组第几个字节开始转换
print(i);//99
#endregion
#region 知识点二 字节数组转字符串类型
//关键类:Encoding
//所在命名空间:System.Text
//主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型
byte[] byteArray2 = Encoding.UTF8.GetBytes("人间自有韬哥在");
string str = Encoding.UTF8.GetString(byteArray2, 0, byteArray2.Length);//第二个参数传从字节数组第几个字节开始转换 第三个参数是长度
print(str);//人间自有韬哥在
#endregion
#region 知识点三 如何将二进制数据转为一个类对象
//1.获取到对应的字节数组
PlayerInfo playerInfo1 = new PlayerInfo();
playerInfo1.lev = 10;
playerInfo1.name = "韬老狮";
playerInfo1.atk = 88;
playerInfo1.sex = false;
byte[] playerInfoBytes1 = playerInfo1.GetBytes();//使用上次封装好得到字节数组的方法得到字节数组
//2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
PlayerInfo playerInfo2 = new PlayerInfo();
//等级
int index = 0;
playerInfo2.lev = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4;//索引加上对应字节
print(playerInfo2.lev);//10
//姓名的长度
int length = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4;//索引加上对应字节
//姓名字符串
playerInfo2.name = Encoding.UTF8.GetString(playerInfoBytes1, index, length);
index += length;//索引加上对应字符串长度
print(playerInfo2.name);//韬老狮
//攻击力
playerInfo2.atk = BitConverter.ToInt16(playerInfoBytes1, index);
index += 2;//索引加上对应字节
print(playerInfo2.atk);//88
//性别
playerInfo2.sex = BitConverter.ToBoolean(playerInfoBytes1, index);
index += 1;//索引加上对应字节
print(playerInfo2.sex);//false
#endregion
#region 总结
//我们对类对象的2进制反序列化主要用到的知识点是
//1.BitConverter转换字节数组为非字符串的类型的变量
//2.Encoding.UTF8转换字节数组为字符串类型的变量(注意:先读长度,再读字符串)
//转换流程是
//1.获取到对应的字节数组
//2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
#endregion
}
}
16.3 练习题
请在上节课写的基类中,加入反序列化相关的操作
完善自定义序列化转换类读取部分
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public abstract class BaseData
{
// 将二进制字节数组反序列化到对象的成员变量中
public abstract int Reading(byte[] bytes, int beginIndex = 0);
//存储省略..
#region 读取
// 根据字节数组读取整数
protected int ReadInt(byte[] bytes, ref int index)
{
int value = BitConverter.ToInt32(bytes, index);
index += sizeof(int);
return value;
}
// 根据字节数组读取短整数
protected short ReadShort(byte[] bytes, ref int index)
{
short value = BitConverter.ToInt16(bytes, index);
index += sizeof(short);
return value;
}
// 根据字节数组读取长整数
protected long ReadLong(byte[] bytes, ref int index)
{
long value = BitConverter.ToInt64(bytes, index);
index += sizeof(long);
return value;
}
// 根据字节数组读取单精度浮点数
protected float ReadFloat(byte[] bytes, ref int index)
{
float value = BitConverter.ToSingle(bytes, index);
index += sizeof(float);
return value;
}
// 根据字节数组读取字节
protected byte ReadByte(byte[] bytes, ref int index)
{
byte value = bytes[index];
index += sizeof(byte);
return value;
}
// 根据字节数组读取布尔值
protected bool ReadBool(byte[] bytes, ref int index)
{
bool value = BitConverter.ToBoolean(bytes, index);
index += sizeof(bool);
return value;
}
// 根据字节数组读取字符串
protected string ReadString(byte[] bytes, ref int index)
{
// 首先读取长度
int length = ReadInt(bytes, ref index);
// 再读取字符串
string value = Encoding.UTF8.GetString(bytes, index, length);
index += length;
return value;
}
// 根据字节数组读取自定义数据对象
protected T ReadData<T>(byte[] bytes, ref int index) where T : BaseData, new()
{
T value = new T();
index += value.Reading(bytes, index);
return value;
}
#endregion
}
完善两个测试类
public class TestInfo : BaseData
{
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex; // 用于记录当前反序列化位置的索引
lev = ReadShort(bytes, ref index); // 反序列化玩家等级
player = ReadData<Player>(bytes, ref index); // 反序列化玩家对象
hp = ReadInt(bytes, ref index); // 反序列化生命值
name = ReadString(bytes, ref index); // 反序列化玩家名称
sex = ReadBool(bytes, ref index); // 反序列化玩家性别
// 反序列化列表的长度以及循环反序列化对应的内容
return index - beginIndex; // 返回已反序列化的字节数
}
}
public class Player : BaseData
{
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex; // 用于记录当前反序列化位置的索引
atk = ReadInt(bytes, ref index); // 反序列化玩家攻击力
return index - beginIndex; // 返回已反序列化的字节数
}
}
进行反序列化测试
// 创建 TestInfo 对象用于存储数据
TestInfo testInfo1 = new TestInfo();
// 设置 TestInfo 对象的各个成员变量的值
testInfo1.lev = 87; // 设置玩家等级
testInfo1.player = new Player(); // 创建 Player 对象并设置玩家对象
testInfo1.player.atk = 77; // 设置玩家攻击力
testInfo1.hp = 100; // 设置生命值
testInfo1.name = "人间自有韬哥在"; // 设置玩家名称
testInfo1.sex = false; // 设置玩家性别
// 调用 TestInfo 对象的 Writing 方法,将数据序列化为字节数组
byte[] testInfoByteArray = testInfo1.Writing();
// 创建一个新的 TestInfo 对象,用于反序列化数据
TestInfo testInfo2 = new TestInfo();
// 调用 TestInfo 对象的 Reading 方法,将字节数组解析为对象
testInfo2.Reading(testInfoByteArray);
// 打印反序列化后的数据
print(testInfo2.lev); //87 打印玩家等级
print(testInfo2.player.atk); //77 打印玩家攻击力
print(testInfo2.hp); //100 打印生命值
print(testInfo2.name); //人间自有韬哥在 打印玩家名称
print(testInfo2.sex); //false 打印玩家性别
16.4 练习题代码
BaseData
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public abstract class BaseData
{
/// <summary>
/// 用于子类重写的 获取字节数组容器大小的方法
/// </summary>
/// <returns>对象需要占用的字节数</returns>
public abstract int GetBytesNum();
/// <summary>
/// 将对象的成员变量序列化为对应的字节数组
/// </summary>
/// <returns>序列化后的字节数组</returns>
public abstract byte[] Writing();
/// <summary>
/// 将二进制字节数组反序列化到对象的成员变量中
/// </summary>
/// <param name="bytes">反序列化使用的字节数组</param>
/// <param name="beginIndex">从该字节数组的第几个位置开始解析,默认为0</param>
/// <returns>已解析的字节数</returns>
public abstract int Reading(byte[] bytes, int beginIndex = 0);
#region 存储
/// <summary>
/// 存储整型变量到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的整数值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteInt(byte[] bytes, int value, ref int index)
{
BitConverter.GetBytes(value).CopyTo(bytes, index);
index += sizeof(int);
}
/// <summary>
/// 存储短整型变量到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的短整数值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteShort(byte[] bytes, short value, ref int index)
{
BitConverter.GetBytes(value).CopyTo(bytes, index);
index += sizeof(short);
}
/// <summary>
/// 存储长整型变量到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的长整数值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteLong(byte[] bytes, long value, ref int index)
{
BitConverter.GetBytes(value).CopyTo(bytes, index);
index += sizeof(long);
}
/// <summary>
/// 存储单精度浮点数到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的单精度浮点数值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteFloat(byte[] bytes, float value, ref int index)
{
BitConverter.GetBytes(value).CopyTo(bytes, index);
index += sizeof(float);
}
/// <summary>
/// 存储字节到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的字节值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteByte(byte[] bytes, byte value, ref int index)
{
bytes[index] = value;
index += sizeof(byte);
}
/// <summary>
/// 存储布尔值到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的布尔值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteBool(byte[] bytes, bool value, ref int index)
{
BitConverter.GetBytes(value).CopyTo(bytes, index);
index += sizeof(bool);
}
/// <summary>
/// 存储字符串到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="value">具体的字符串值</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteString(byte[] bytes, string value, ref int index)
{
// 先存储字符串字节数组的长度
byte[] strBytes = Encoding.UTF8.GetBytes(value);
WriteInt(bytes, strBytes.Length, ref index);
// 再存储字符串字节数组
strBytes.CopyTo(bytes, index);
index += strBytes.Length;
}
/// <summary>
/// 存储自定义数据对象到指定的字节数组中
/// </summary>
/// <param name="bytes">指定的字节数组</param>
/// <param name="data">具体的自定义数据对象</param>
/// <param name="index">每次存储后用于记录当前索引位置的变量</param>
protected void WriteData(byte[] bytes, BaseData data, ref int index)
{
data.Writing().CopyTo(bytes, index);
index += data.GetBytesNum();
}
#endregion
#region 读取
/// <summary>
/// 根据字节数组读取整数
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的整数值</returns>
protected int ReadInt(byte[] bytes, ref int index)
{
int value = BitConverter.ToInt32(bytes, index);
index += sizeof(int);
return value;
}
/// <summary>
/// 根据字节数组读取短整数
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的短整数值</returns>
protected short ReadShort(byte[] bytes, ref int index)
{
short value = BitConverter.ToInt16(bytes, index);
index += sizeof(short);
return value;
}
/// <summary>
/// 根据字节数组读取长整数
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的长整数值</returns>
protected long ReadLong(byte[] bytes, ref int index)
{
long value = BitConverter.ToInt64(bytes, index);
index += sizeof(long);
return value;
}
/// <summary>
/// 根据字节数组读取单精度浮点数
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的单精度浮点数值</returns>
protected float ReadFloat(byte[] bytes, ref int index)
{
float value = BitConverter.ToSingle(bytes, index);
index += sizeof(float);
return value;
}
/// <summary>
/// 根据字节数组读取字节
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的字节值</returns>
protected byte ReadByte(byte[] bytes, ref int index)
{
byte value = bytes[index];
index += sizeof(byte);
return value;
}
/// <summary>
/// 根据字节数组读取布尔值
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的布尔值</returns>
protected bool ReadBool(byte[] bytes, ref int index)
{
bool value = BitConverter.ToBoolean(bytes, index);
index += sizeof(bool);
return value;
}
/// <summary>
/// 根据字节数组读取字符串
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>读取到的字符串值</returns>
protected string ReadString(byte[] bytes, ref int index)
{
// 首先读取长度
int length = ReadInt(bytes, ref index);
// 再读取字符串
string value = Encoding.UTF8.GetString(bytes, index, length);
index += length;
return value;
}
/// <summary>
/// 根据字节数组读取自定义数据对象
/// </summary>
/// <typeparam name="T">自定义数据对象的类型</typeparam>
/// <param name="bytes">字节数组</param>
/// <param name="index">开始读取的索引数</param>
/// <returns>已读取的自定义数据对象</returns>
protected T ReadData<T>(byte[] bytes, ref int index) where T : BaseData, new()
{
T value = new T();
index += value.Reading(bytes, index);
return value;
}
#endregion
}
TestInfo
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class TestInfo : BaseData
{
public short lev; // 玩家等级
public Player player; // 玩家对象
public int hp; // 生命值
public string name; // 玩家名称
public bool sex; // 玩家性别
public override int GetBytesNum()
{
// 返回对象需要占用的字节数,按照各个成员的大小累加
return sizeof(short) + // 2 字节
player.GetBytesNum() + // 玩家对象序列化大小
sizeof(int) + // 4 字节
4 + Encoding.UTF8.GetBytes(name).Length + // 字符串长度(4字节) + 字符串字节数
sizeof(bool); // 1 字节
}
public override byte[] Writing()
{
int index = 0; // 用于记录当前序列化位置的索引
byte[] bytes = new byte[GetBytesNum()]; // 创建字节数组
WriteShort(bytes, lev, ref index); // 序列化玩家等级
WriteData(bytes, player, ref index); // 序列化玩家对象
WriteInt(bytes, hp, ref index); // 序列化生命值
WriteString(bytes, name, ref index); // 序列化玩家名称
WriteBool(bytes, sex, ref index); // 序列化玩家性别
// 序列化列表的长度以及循环序列化对应的内容
return bytes; // 返回序列化后的字节数组
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex; // 用于记录当前反序列化位置的索引
lev = ReadShort(bytes, ref index); // 反序列化玩家等级
player = ReadData<Player>(bytes, ref index); // 反序列化玩家对象
hp = ReadInt(bytes, ref index); // 反序列化生命值
name = ReadString(bytes, ref index); // 反序列化玩家名称
sex = ReadBool(bytes, ref index); // 反序列化玩家性别
// 反序列化列表的长度以及循环反序列化对应的内容
return index - beginIndex; // 返回已反序列化的字节数
}
}
public class Player : BaseData
{
public int atk; // 玩家攻击力
public override int GetBytesNum()
{
return 4; // 返回对象需要占用的字节数,此处为 4 字节
}
public override byte[] Writing()
{
int index = 0; // 用于记录当前序列化位置的索引
byte[] bytes = new byte[GetBytesNum()]; // 创建字节数组
WriteInt(bytes, atk, ref index); // 序列化玩家攻击力
return bytes; // 返回序列化后的字节数组
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex; // 用于记录当前反序列化位置的索引
atk = ReadInt(bytes, ref index); // 反序列化玩家攻击力
return index - beginIndex; // 返回已反序列化的字节数
}
}
Lesson16_练习题
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson16_练习题 : MonoBehaviour
{
void Start()
{
// 创建 TestInfo 对象用于存储数据
TestInfo testInfo1 = new TestInfo();
// 设置 TestInfo 对象的各个成员变量的值
testInfo1.lev = 87; // 设置玩家等级
testInfo1.player = new Player(); // 创建 Player 对象并设置玩家对象
testInfo1.player.atk = 77; // 设置玩家攻击力
testInfo1.hp = 100; // 设置生命值
testInfo1.name = "人间自有韬哥在"; // 设置玩家名称
testInfo1.sex = false; // 设置玩家性别
// 调用 TestInfo 对象的 Writing 方法,将数据序列化为字节数组
byte[] testInfoByteArray = testInfo1.Writing();
// 创建一个新的 TestInfo 对象,用于反序列化数据
TestInfo testInfo2 = new TestInfo();
// 调用 TestInfo 对象的 Reading 方法,将字节数组解析为对象
testInfo2.Reading(testInfoByteArray);
// 打印反序列化后的数据
print(testInfo2.lev); //87 打印玩家等级
print(testInfo2.player.atk); //77 打印玩家攻击力
print(testInfo2.hp); //100 打印生命值
print(testInfo2.name); //人间自有韬哥在 打印玩家名称
print(testInfo2.sex); //false 打印玩家性别
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com