【Unity】数据持久化--二进制 ,文件操作

1、各字节类型转字节数组

1.1 不同变量类型

  • 有符号 sbyte int short long
  • 无符号 byte uint ushort ulong
  • 浮点 float double decimal
  • 特殊 bool char string

1.2 变量的本质

  • 变量的本质是2进制
  • 在内存中都以字节的形式存储着
  • 1byte = 8bit
  • 1bit(位)不是0就是1
        //通过sizeof方法可以看到常用变量类型占用的字节空间长度
        print("有符号");
        print("sbyte" + sizeof(sbyte) + "字节");
        print("int" + sizeof(int) + "字节");
        print("short" + sizeof(short) + "字节");
        print("long" + sizeof(long) +"字节");
        print("无符号");
        print("byte" + sizeof(byte) + "字节");
        print("uint" + sizeof(uint) + "字节");
        print("ushort" + sizeof(ushort) +"字节");
        print("ulong" + sizeof(ulong) + "字节");
        print("浮点");
        print("float" + sizeof(float) + "字节");
        print("double" + sizeof(double) + "字节");
        print("decimal" + sizeof(decimal) + "字节");
        print("特殊");
        print("bool" + sizeof(bool) + "字节");
        print("char" + sizeof(char) + "字节");

1.3 二进制文件读写的本质

  • 它就是通过将各类型变量转换为字节数组
  • 将字节数组直接存储到文件中
  • 一般人是看不懂存储的数据的
  • 不仅可以节约存储空间,提升效率
  • 还可以提升安全性
  • 而且在网络通信中我们直接传输的数据也是字节数据 (2进制数据)

1.4 各类型数据和字节数据相互转换

  • C#提供了一个公共类帮助我们进行转化
  • 类名:BitConverter
  • 命名空间:using system

将各类型转字节

BitConverter.GetBytes()

字节数组转各类型,不同类型使用不同方法

BitConverter.ToInt32 (bytes, 0)

        BitConverter.ToBoolean (bytes, 1);
        BitConverter.ToChar (bytes, 1);
        BitConverter.ToDouble (bytes, 1);
        BitConverter.ToInt16 (bytes, 1);
        BitConverter.ToInt32 (bytes, 1);
        BitConverter.ToInt64 (bytes, 1);
        BitConverter.ToSingle (bytes, 1);
        BitConverter.ToUInt16 (bytes, 1);
        BitConverter.ToUInt32 (bytes, 1);
        BitConverter.ToUInt64 (bytes, 1);

        //1.将各类型转字节
        byte[] bytes = BitConverter.GetBytes(256);
        //2.字节数组转各类型
        int i = BitConverter.ToInt32 (bytes, 0);
        Debug.Log(i);

1.5 标准编码格式

  • 编码是用预先规定的方法将文字、 数字或其它对象编成数码,或将信息、 数据转换成规定的电脉冲信号
  • 为保证编码的正确性,编码要规范化、标准化,即需有标准的编码格式。
  • 常见的编码格式有ASCII、ANSI、GBK、GB2312、UTF-8、GB18030和UNICODE等

直白解释

  • 计算机中数据的本质就是2进制数据
  • 编码格式就是用对应的2进制数 对应不同的文字
  • 由于世界上有各种不同的语言,所有会有很多种不同的编码格式
  • 不同的编码格式 对应的规则是不同的
  • 如果在读取字符时采用了不统一的编码格式,可能会出现乱码

  • 游戏开发中常编码格式 UTF-8
  • 中文相关编码格式 GBK
  • 英文相关编码格式 ASCII
  • 在c#中有一个专门的编码格式类 来帮助我们将字符串和字节数组进行转换

类名:Encoding

需要引用命名空间:using system.Text;

将字符串以指定编码格式转字节

Encoding.UTF8.GetBytes(字符串)

字节数组以指定编码格式转字符串

Encoding.UTF8.GetString (字节数组)

        //1.将字符串以指定编码格式转字节
        byte[] bytes2 = Encoding.UTF8.GetBytes("周涛涛");

        //2.字节数组以指定编码格式转字符串
        string s = Encoding.UTF8.GetString (bytes2);
        //string s = Encoding.UTF8.GetString (bytes2, 0, bytes2.Length);
        Debug.Log(s);

2、文件操作相关

2.1 代码中的文件操作

  • 在电脑上我们可以在操作系统中创建删除修改文件
  • 可以增删查改各种各样的文件类型
  • 代码中的文件操作就是通过代码来做这些事情

2.2 文件相关操作公共类

  • C#提供了一个名为File(文件)的公共类
  • 让我们可以快捷的通过代码操作文件相关
  • 类名:Fi1e
  • 命名空间: System.Io

2.3 文件操作File类的常用内容

判断文件是否存在        File.Exists("文件路径")

创建文件        File.Create("文件路径")

写入文件        File.WriteAllBytes("路径", 字节数组)

读取文件        

读取字节数据        File.ReadAllBytes("路径")

读取所有行信息        File.ReadAllLines("路径")

读取所有文本信息        File.ReadAllText("路径")

删除文件        File.Delete("路径")

复制文件        File.Copy(现有文件, 目标文件)

文件替换        File.Replace(用来替换的路径, 被替换的路径, 备份路径)

以流的形式 打开文件并写入或读取 FileStream fs = File.Open(路径, 打开模式, 访问模式)

        //1.判断文件是否存在
        //File.Exists("文件路径");
        if(File.Exists(Application.persistentDataPath + "/文件"))
        {
            Debug.Log("文件存在");
        }
        else
        {
            Debug.Log("文件不存在");
        }

        //2.创建文件
        //FileStream fs = File.Create(Application.dataPath + "/UnityTeach.tang");

        //3.写入文件
        //将指定字节数组 写入到指定路径的文件中
        byte[] bytes = BitConverter.GetBytes(999);
        File.WriteAllBytes(Application.dataPath + "/UnityTeach.tang", bytes);

        //将指定的string数组内容 一行行写入到指定路径中
        string[] strs = new string[] { "123", "2313", "2323" };
        File.WriteAllLines(Application.dataPath + "/UnityTeach2.tang", strs);

        //将指定字符串写入指定路径
        File.WriteAllText(Application.dataPath + "/UnityTeach3.tang", "小帅哥12313\n\t说的就是简单介绍");

        //4.读取文件
        //读取字节数据
        bytes = File.ReadAllBytes(Application.dataPath + "/UnityTeach.tang");
        Debug.Log(BitConverter.ToInt32(bytes, 0));

        //读取所有行信息
        strs = File.ReadAllLines(Application.dataPath + "/UnityTeach2.tang");
        for (int i = 0; i < strs.Length; i++)
        {
            Debug.Log(strs[i]);
        }

        //读取所有文本信息
        string s = File.ReadAllText(Application.dataPath + "/UnityTeach3.tang");
        Debug.Log(s);

        //5.删除文件
        //注意 如果删除打开着的文件 会报错
        File.Delete(Application.dataPath + "/UnityTeach.tang");

        //6.复制文件
        //参数一:现有文件 需要是流关闭状态
        //参数二:目标文件
        File.Copy(Application.dataPath + "/UnityTeach2.tang", Application.dataPath + "/唐老师.tanglaoshi", true);

        //7.文件替换
        //参数一: 用来替换的路径
        //参数二: 被替换的路径
        //参数三: 备份路径
        File.Replace(Application.dataPath + "/UnityTeach2.tang", Application.dataPath + "/唐老师.tanglaoshi", Application.dataPath + "/唐老师备份.tanglaoshi");

        //8.以流的形式 打开文件并写入或读取
        //参数一:路径
        //参数二:打开模式
        //参数三:访问模式
        FileStream fs = File.Open(Application.dataPath + "/UnityTeach2.tang", FileMode.OpenOrCreate, FileAccess.ReadWrite);

2.4 总结

  • File类提供了各种方法帮助我们进行文件的基础操作,需要记住这些关键API
  • 一般情况下想要整体读写内容 可以使用File提供的write和Read相关功能

3、文件流操作相关

3.1 什么是文件流

  • 在C#中提供了一个文件流类 FileStream类
  • 它主要作用是用于读写文件的细节
  • 我们之前学过的Fi1e只能整体读写文件
  • 而Filestream可以以读写字节的形式处理文件

直白解释

  • 文件里面存储的数据就像是一条数据流(数组或者列表)
  • 我们可以通过FileStream 一部分一部分的读写数据流
  • 比如我可以先存一个int(4个字节)再存一个bo01(1个字节)再存一个string(n个字节)
  • 利用Filestream可以以流式逐个读写

3.2 FileStream文件流类常用方法

  • 类名:FileStream
  • 需要引用命名空间:system.

3.2.1 打开或创建指定文件

方法一 new FileStream

  • 参数一:路径
  • 参数二:打开模式
    • CreateNew: 创建新文件 如果文件存在 则报错
    • Create: 创建文件,如果文件存在 则覆盖
    • Open: 打开文件,如果文件不存在 报错
    • Openorcreate: 打开或者创建文件根据实际情况操作
    • Append:若存在文件,则打开并查找文件尾,或者创建一个新文件
    • Truncate:打开并清空文件内容
  • 参数三:访问模式
  • 参数四:共享权限
    • None 谢绝共享
    • Read 允许别的程序读取当前文件
    • Write 允许别的程序写入该文件
    • Readwrite 允许别的程序读写该文件
FileStream fs = new FileStream(Application.dataPath + "/Lesson3.tang", FileMode.Create, FileAccess.ReadWrite);

方法二: File.create

  • 参数一: 路径
  • 参数二: 缓存大小
  • 参数三:描述如何创建或覆盖该文件(不常用)
    • Asynchronous 可用于异步读写
    • DeleteOnClose 不在使用时,自动删除
    • Encrypted 加密
    • None 不应用其它选项
    • RandomAccess 随机访问文件
    • SequentialScan 从头到尾顺序访问文件
    • WriteThrough 通过中间缓存直接写入磁盘 
FileStream fs2 = File.Create(Application.dataPath + "/Lesson3.tang", 2048);

方法三: File.open

  • 参数一: 路径
  • 参数二: 打开模式
FileStream fs3 = File.Open(Application.dataPath + "/Lesson3.tang", FileMode.Open);

3.2.3 重要属性和方法

文本字节长度        fs.Length

是否可写        fs.CanWrite

是否可读        fs.CanRead

将字节写入文件 当写入后 一定执行一次        fs.Flush();

关闭流 当文件读写完毕后 一定执行        fs.Close();

缓存资源销毁回收        fs.Dispose();

3.2.4 写入字节

using(FileStream fs = new FileStream(Application.persistentDataPath + "/Lesson3.tang", FileMode.OpenOrCreate, FileAccess.ReadWrite))
        {
            Debug.Log(Application.persistentDataPath);

            byte[] bytes = BitConverter.GetBytes(9999);
            //方法:write
            //参数一: 写入的字节数组
            //参数二: 数组中的开始索引
            //参数三:写入多少个字节
            fs.Write(bytes, 0, 4);

            //写入字符串时
            bytes = Encoding.UTF8.GetBytes("你是个啥子");
            //先写入长度
            int length = bytes.Length;
            fs.Write(BitConverter.GetBytes(length), 0, 4);
            //再写入字符串具体内容
            fs.Write(bytes, 0, length);

            //避免数据丢失 一直要执行一次的方法
            fs.Flush();
            fs.Dispose();
        }

读取字节

        #region 方法一:挨个读取字节数组
        using(FileStream fs2 = File.Open(Application.persistentDataPath + "/Lesson3.tang", FileMode.Open, FileAccess.Read))
        {
            //读取第一个整形
            byte[] bytes2 = new byte[4];

            //参数一: 用于存储读取的字节数组的容器
            //参数二: 容器中开始的位置
            //参数三: 读取多少个字节装入容器
            //返回值: 当前流索引前进了几个位置
            int index = fs2.Read(bytes2, 0, 4);
            int i = BitConverter.ToInt32(bytes2, 0);
            Debug.Log("取出来的第一个整数" + i);//9999
            Debug.Log("索引向前移动" + index + "个位置");

            //读取第二个字符串
            //读取字符串字节数组长度
            index = fs2.Read(bytes2, 0, 4);
            Debug.Log("索引向前移动" + index + "个位置");
            int length2 = BitConverter.ToInt32(bytes2, 0);
            //要根据我们存储的字符串字节数组长度 来声明一个字节数组 用来装载读取出来的数据
            bytes2 = new byte[length2];
            index = fs2.Read(bytes2, 0, length2);
            Debug.Log("索引向前移动" + index + "个位置");
            //得到最终的字符串 打印出来
            Debug.Log(Encoding.UTF8.GetString(bytes2));

            fs2.Dispose();
        }
        

        #endregion

        #region 方法二:一次性读取再挨个读取
        Debug.Log("***********************");
        using(FileStream fs3 = File.Open(Application.persistentDataPath + "/Lesson3.tang", FileMode.OpenOrCreate, FileAccess.Read))
        {
            //一开始就声明一个 和文件字节数组长度一样的容器
            byte[] bytes3 = new byte[fs3.Length];
            fs3.Read(bytes3, 0, (int)fs3.Length);
            fs3.Dispose();
            //读取整数
            Debug.Log(BitConverter.ToInt32(bytes3, 0));
            //读取字符串字节数组长度
            int length3 = BitConverter.ToInt32(bytes3, 4);
            //得到字符串
            Debug.Log(Encoding.UTF8.GetString(bytes3, 8, length3));
        }


        #endregion

3.3 更加安全的使用文件流对象

  • using头键字重要用法
  • using(申明一个引用对象)
  • {
  •         使用对象
  • }
  • 无论发生什么情况 当using语句块结束后
  • 会自动调用该对象的销毁方法 避免忘记销毁或关闭流
  • using是一种更安全的使用方法

强调:

目前我们对文件流进行操作 为了文件操作安全 都用using来进行处理最好

3.4 总结

  • 通过FIlestream读写时一定要注意
  • 读的规则一定是要和写是一致的
  • 我们存储数据的先后顺序是我们制定的规则
  • 只要按照规则读写就能保证数据的正确性

4、文件夹操作相关

4.1 文件夹操作是指什么

  • 平时我们可以在操作系统的文件管理系统中
  • 通过一些操作增删查改文件夹
  • 我们目前要学习的就是通过代码的形式
  • 来对文件夹进行增删查改的操作

4.2 C#提供给我们的文件夹操作公共类

  • 类名:Directory
  • 命名空间:using system.I0
  • 判断文件夹是否存在        Directory.Exists
  • 创建文件夹        Directory.CreateDirectory
  • 删除文件夹        Directory.Delete(路径, 是否删除非空目录)
  • 查找文件夹和文件
    • 得到指定路径下所有文件夹名        Directory.GetDirectories
    • 得到指定路径下所有文件名        Directory.GetFiles
  • 移动文件夹        Directory.Move()  如果第二个参数所在的路径 已经存在了一个文件夹 那么会报错

4.3 DirectoryInfo和FileInfo

  • DirectoryInfo日录信息类
  • 我们可以通过它获取文件夹的更多信息

它主要出现在两个地方

创建文件夹方法的返回值

        DirectoryInfo dInfo = Directory.CreateDirectory(Application.dataPath + "/数据持久化122");
        //全路径
        Debug.Log(dInfo.FullName);
        //文件名
        Debug.Log(dInfo.Name);

查找上级文件夹信息

        dInfo = Directory.GetParent(Application.dataPath + "/数据持久化122");
        //全路径
        Debug.Log(dInfo.FullName);
        //文件名
        Debug.Log(dInfo.Name);

得到所有子文件夹的目录信息

        DirectoryInfo[] dInfos = dInfo.GetDirectories();
  • FileInfo文件信息类

我们可以通过DirectoryInfo得到该文件下的所有文件信息

        FileInfo[] fInfos = dInfo.GetFiles();
        foreach (FileInfo fi in fInfos)
        {
            Debug.Log("******************");
            Debug.Log(fi.Name);//文件名
            Debug.Log(fi.FullName);//路径
            Debug.Log(fi.Length);//字节长度
            Debug.Log(fi.Extension);//后缀名
        }

5、C#类对象的序列化

5.1 序列化类对象第一步-申明类对象

注意:如果要使用c#自带的序列化2进制方法

申明类时需要添加[System.Serializable]特性

5.2 序列化类对象第二步-将对象进行2进制序列化

方法一:使用内存流得到2进制字节数组
主要用于得到字节数组 可以用于网络传输

  • 内存流对象
    • 类名:MemoryStream
    • 命名空间:system.I0
  • 进制格式化对象
    • 类名:BinaryFormatter
    • 命名空间:System.Runtime.Serialization.Formatters.Binary.
    • 主要方法:序列化方法 Serialize
        using (MemoryStream ms = new MemoryStream())
        {
            //二进制格式化程序
            BinaryFormatter bf = new BinaryFormatter();
            //序列化对象 生成2进制字节数组 写入到内存流当中
            bf.Serialize(ms, p);

            //得到对象的二进制字节数组
            byte[] bytes = ms.GetBuffer();
            //存储字节
            File.WriteAllBytes(Application.dataPath + "/Lesson5.zhou", bytes);

            //关闭内存流
            ms.Close();
        }

方法二:使用文件流进行存储
主要用于存储到文件中

        using(FileStream fs = new FileStream(Application.dataPath + "/Lesson5_2.zhou", FileMode.OpenOrCreate, FileAccess.Write))
        {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, p);

            fs.Flush();
            fs.Close();
        }

5.3 总结

c#提供的类对象2进制序列化主要类是 BinaryFormatter
通过其中的序列化方法即可进行序列化生成字节数组

6、C#类对象的反序列化

6.1 反序列化文件中数据

  • 主要类
    • Filestream文件流类
    • BinaryFormatter 2进制格式化类
  • 主要方法
    • Deserizlize
        //通过文件流打开指定的2进制数据文件
        using (FileStream fs = new FileStream(Application.dataPath + "/Lesson5_2.zhou", FileMode.Open, FileAccess.Read))
        {
            //声明一个 二进制格式化类
            BinaryFormatter bf = new BinaryFormatter();
            //反序列化
            Person p = bf.Deserialize(fs) as Person;

            fs.Close();
        }

6.2 反序列化网络传输过来的2进制数据

  • 主要类
    • Memorysteeam内存流类
    • BinaryFormatter 2进制格式化类
  • 主要方法
    • Deserizlize
        //日前没有网络传输 我们还是直接从文件中获取

        byte[] bytes = File.ReadAllBytes(Application.dataPath + "/Lesson5_2.zhou");
        //声明内存流对象 一开始就把字节数组传输进去
        using(MemoryStream ms = new MemoryStream(bytes))
        {
            //声明一个 二进制格式化类
            BinaryFormatter bf = new BinaryFormatter();
            //反序列化
            Person p = bf.Deserialize(ms) as Person;

            ms.Close();
        }

7、二进制数据加密

7.1 何时加密?何时解密?

  • 当我们将类对象转换为2进制数据时进行加密
  • 当我们将2进制数据转换为类对象时进行解密
  • 这样如果第三方获取到我们的2进制数据
  • 当他们不知道加密规则和解密秘钥时就无法获取正确的数据
  • 起到保证数据安全的作用

7.2 加密是否是100%安全?

  • 一定记住加密只是提高破解门槛,没有100%保密的数据
  • 通过各种尝试始终是可以破解加密规则的,只是时间问题
  • 加密只能起到提升一定的安全性

7.3 常用加密算法

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

7.4 用简单的异或加密感受加密的作用

        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 + "/Lesson7.zhou", bytes);
        }

        byte[] bytes1 = File.ReadAllBytes(Application.dataPath + "/Lesson7.zhou");
        for(int i = 0;i < bytes1.Length; i++)
        {
            bytes1[i] ^= key;
        }
        using (MemoryStream ms = new MemoryStream(bytes1))
        {
            BinaryFormatter bf = new BinaryFormatter();
            Person p2 = bf.Deserialize(ms) as Person;
        }

8、二进制数据管理器

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

/// <summary>
/// 2进制数据管理器
/// </summary>
public class BinaryDataMgr
{
    private static BinaryDataMgr instance = new BinaryDataMgr();
    public static BinaryDataMgr Instance => instance;
    private BinaryDataMgr() { }

    private static string SAVE_PATH = Application.persistentDataPath + "/Data/";

    /// <summary>
    /// 存储类对象数据
    /// </summary>
    /// <param name="data"></param>
    /// <param name="fileName"></param>
    public void Save(object data, string fileName)
    {
        if(!Directory.Exists(SAVE_PATH))
        {
            Directory.CreateDirectory(SAVE_PATH);
        }

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

            fs.Flush();
            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 + ".zhou"))
        {
            return default(T);
        }

        T data;
        using(FileStream fs = new FileStream(SAVE_PATH + fileName + ".zhou", FileMode.Open, FileAccess.Read))
        {
            BinaryFormatter bf = new BinaryFormatter();
            data = bf.Deserialize(fs) as T;
            fs.Close();
        }
        return data;
    }

}

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值