Contents

二进制序列化

基础类型(非字符串)

关键类:BitConverter

命名空间:System

主要作用:除字符串的其他常用类型和字节数组互相转换

非字符串类型转二进制

byte[] bytes = BitConverter.GetBytes(1);

二进制转非字符串类型

//int类型
int i = BitConverter.ToInt32(bytes);
//重载 从数组的索引读取
i = BitConverter.ToInt32(bytes, index);

字符串

关键类:Encoding

命名空间:System.Text

主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议使用UTF-8

字符串转二进制

byte[] bytes = Encoding.UTF8.GetBytes("字符串转字节数组");

二进制转字符串

string str = Encoding.UTF8.GetString(bytes);
//重载 指定位置和长度
str = Encoding.UTF8.GetString(bytes, 0, bytes.Length);

类对象

自定义规则转换

通过BitConverterEncoding进行转换

网络通信中我们不能直接使用BinaryFormatter2进制格式化类

因为客户端和服务器使用的语言可能不一样,BinaryFormatter是C#的序列化规则,和其它语言之间的兼容性不好

如果使用它,那么其它语言开发的服务器无法对其进行反序列化,我们可以自定义规则来进行转换。

声明数据结构类

public class PlayerInfo
{
    public int lev;//4个字节
    public string name;//先存一个int把字符串的长度存进去 再存字符串
    public short atk;//2个字节
    public bool sex;//1个字节
}

类对象转二进制

public byte[] GetBytes(PlayerInfo playerInfo)
{
    //得到字节数组容器的容量
    int indexNum = sizeof(int) +
                  sizeof(int) + Encoding.UTF8.GetBytes(playerInfo.name).Length +
                  sizeof(short) +
                  sizeof(bool);
    //声明字节数组
    byte[] playerBytes = new byte[indexNum];
    //使用CopyTo方法 将转换的字节数组存入容器中 第二个参数代表索引
    int index = 0;
    //存入等级
    BitConverter.GetBytes(playerInfo.lev).CopyTo(playerBytes,index);
    index += sizeof(int);
    //存名字
    byte[] nameBytes = Encoding.UTF8.GetBytes(playerInfo.name);
    BitConverter.GetBytes(nameBytes.Length).CopyTo(playerBytes,index);//存名字长度
    index += sizeof(int);
    nameBytes.CopyTo(playerBytes,index);//存名字
    index += nameBytes.Length;
    //存攻击力
    BitConverter.GetBytes(playerInfo.atk).CopyTo(playerBytes,index);
    index += sizeof(short);
    //存性别
    BitConverter.GetBytes(playerInfo.sex).CopyTo(playerBytes,index);
    return playerBytes;
}

二进制转类对象

public byte[] GetPlayerInfo(byte[] playerBytes)
{
    PlayerInfo playerInfo = new();
    //定义索引
    int index = 0;
    //获取等级
    playerInfo.lev = BitConverter.ToInt32(playerBytes, index);
    index += sizeof(int);
    //获取名字
    //先取长度
    int length = BitConverter.ToInt32(playerBytes, index);
    index += sizeof(int);
    //再取名字
    playerInfo.name = Encoding.UTF8.GetString(playerBytes, index, length);
    index += length;
    //获取攻击力
    playerInfo.atk = BitConverter.ToInt16(playerBytes, index);
    index += sizeof(short);
    //取性别
    playerInfo.sex = BitConverter.ToBoolean(index);
    return playerInfo;
}

BinaryFormatter序列化类

BinaryFormatter是C#提供的序列化类,可以很方便的将类对象序列化成二进制

声明数据结构类

[System.Serializable]
//通过`BinaryFormatter`序列化类时 需要为该类添加[System.Serializable]特性
public class Person
{
    public int age = 1;
    public string name = "路飞";
    public int[] ints = new int[] { 1, 2, 3, 4, 5 };
    public List<int> list = new List<int>() { 1, 2, 3, 4 };
    public Dictionary<int, string> dic = new Dictionary<int, string>() { { 1,"123"},{ 2,"1223"},{ 3,"435345" } };
    public StructTest st = new StructTest(2, "123");
    public ClssTest ct = new ClssTest();
}

类对象转二进制

使用内存流存储

Person p = new Person();
using (MemoryStream ms = new MemoryStream())
{
    //2进制格式化程序
    BinaryFormatter bf = new BinaryFormatter();
    //序列化对象 生成2进制字节数组 写入到内存流当中
    bf.Serialize(ms, p);
    //得到对象的2进制字节数组
    byte[] bytes = ms.GetBuffer();
    //存储字节
    File.WriteAllBytes(Application.dataPath + "/Test.test", bytes);
    //关闭内存流
    ms.Close();
}

使用文件流存储

Person p = new Person();
using (FileStream fs = new FileStream(Application.dataPath + "/Test.test", FileMode.OpenOrCreate, FileAccess.Write))
{
    //2进制格式化程序
    BinaryFormatter bf = new BinaryFormatter();
    //序列化对象 生成2进制字节数组 写入到文件流当中
    bf.Serialize(fs, p);
    fs.Flush();
    fs.Close();
}

二进制转类对象

使用文件流打开2进制数据文件

using (FileStream fs = File.Open(Application.dataPath + "/Test.test", FileMode.Open, FileAccess.Read))
{
    //申明一个 2进制格式化类
    BinaryFormatter bf = new BinaryFormatter();
    //反序列化
    Person p = bf.Deserialize(fs) as Person;
    fs.Close();
}

使用网络传输过来的2进制数据

//目前没有网络传输 我们还是直接从文件中获取
byte[] bytes = File.ReadAllBytes(Application.dataPath + "/Test.test");
//申明内存流对象 一开始就把字节数组传输进去
using (MemoryStream ms = new MemoryStream(bytes))
{
    //申明一个 2进制格式化类
    BinaryFormatter bf = new BinaryFormatter();
    //反序列化
    Person p = bf.Deserialize(ms) as Person;
    ms.Close();
}

加密存储

何时加密?何时解密?

当我们将类对象转换为2进制数据时进行加密

当我们将2进制数据转换为类对象时进行解密

常用加密算法

  • MD5算法
  • SHA1算法
  • HMAC算法
  • AES/DES/3DES算法等

用简单的异或加密

Person p = new Person();
byte key = 199;
using (MemoryStream ms = new MemoryStream())
{
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(ms, p);
    byte[] bytes = ms.GetBuffer();
    //异或加密
    for (int i = 0; i < bytes.Length; i++)
    {
        bytes[i] ^= key;
    }
    File.WriteAllBytes(Application.dataPath + "/Test.test", bytes);
}

//解密
byte[] bytes2 = File.ReadAllBytes(Application.dataPath + "/Test.test");
for (int i = 0; i < bytes2.Length; i++)
{
    bytes2[i] ^= key;
}
using (MemoryStream ms = new MemoryStream(bytes2))
{
    BinaryFormatter bf = new BinaryFormatter();
    Person p2 = bf.Deserialize(ms) as Person;
    ms.Close();
}

封装序列化管理类

public class BinaryDataMgr
{
    
//单例   
private static readonly Lazy<BinaryDataMgr> instance = new(() => new BinaryDataMgr());
public static BinaryDataMgr Instance => instance.Value;
private BinaryDataMgr(){}

/// <summary>
/// 数据存储的位置
/// </summary>
private static string SAVE_PATH = Application.persistentDataPath + "/Data/";
    
/// <summary>
/// 存储类对象数据
/// </summary>
/// <param name="obj"></param>
/// <param name="fileName"></param>
public void Save(object obj, string fileName)
{
    //先判断路径文件夹有没有
    if (!Directory.Exists(SAVE_PATH))
        Directory.CreateDirectory(SAVE_PATH);

    using (FileStream fs = new FileStream(SAVE_PATH + fileName + ".data", FileMode.OpenOrCreate, FileAccess.Write))
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(fs, obj);
        fs.Close();
    }
}

/// <summary>
/// 读取2进制数据转换成对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="fileName"></param>
/// <returns></returns>
public T Load<T>(string fileName) where T:class
{
    //如果不存在这个文件 就直接返回泛型对象的默认值
    if( !File.Exists(SAVE_PATH + fileName + ".data") )
        return default(T);
    T obj;
    using (FileStream fs = File.Open(SAVE_PATH + fileName + ".data", FileMode.Open, FileAccess.Read))
    {
        BinaryFormatter bf = new BinaryFormatter();
        obj = bf.Deserialize(fs) as T;
        fs.Close();
    }
    return obj;
}
}