青岛建设官方网站,南宁制作企业网站,风中有朵雨做的云在线网站,做网站499目录
什么是数据持久化
数据持久化之PlayerPrefs 概述
API及用法
电脑中存放的位置
优缺点
主要用处
封装PlayerPrefs
数据持久化之XML
XML是什么
读取XML信息
C#读取XML的方法有几种
读取xml文件信息 读取元素和属性信息
总结
写入XML信息
选择存储目录
存储…目录
什么是数据持久化
数据持久化之PlayerPrefs 概述
API及用法
电脑中存放的位置
优缺点
主要用处
封装PlayerPrefs
数据持久化之XML
XML是什么
读取XML信息
C#读取XML的方法有几种
读取xml文件信息 读取元素和属性信息
总结
写入XML信息
选择存储目录
存储xml文件
修改xml文件
总结
XML序列化
什么是序列化和反序列化
xml序列化
自定义节点名 或 设置属性
总结
XML反序列化
自定义类继承IXmlSerializable
扩展Dictionary支持序列号反序列化
封装
优缺点
优点
缺点
主要用处
数据持久化之Json
Json是什么
Json和Xml的异同
JsonUnity 在文件中存读字符串
JsonUnity序列化
JsonUtlity进行反序列化 注意事项 总结
LitJson
使用LitJson进行序列化
使用LitJson反序列化 注意事项
总结
封装
数据持久化之2进制 2进制是什么 数据持久化之2进制的好处
各类型数据转字节数据 2进制文件读写的本质 数据和字节数据相互转换
文件操作相关 1.判断文件是否存在 2.创建文件 3.写入文件 4.读取文件 5.删除文件 6.复制文件 7.文件替换 8.以流的形式 打开文件并写入或读取
文件操作相关文件流 什么是文件流 1.打开或创建指定文件 2.重要属性和方法 3.写入字节 4.读取字节
文件夹相关操作 1.判断文件夹是否存在 2.创建文件夹 3.删除文件夹 4.查找文件夹和文件 5.移动文件夹 DirectoryInfo和FileInfo
c#类对象的序列化 方法一使用内存流得到2进制字节数组 方法二使用文件流进行存储
c#类对象的反序列化
c#2进制数据加密
Unity添加菜单栏按钮
Excel DLL包的导入
Excel数据读取 打开Excel表 获取Excel表中单元格的信息
封装 ExcelTool BinaryDataMgr
结尾 什么是数据持久化 数据持久化就是将内存中的数据模型转换为存储模型以及将存储模型转换为内存中的数据模型的统称 通俗来讲就是将数据存到硬盘硬盘中数据读到游戏中也就是传统意义上的存盘
数据持久化之PlayerPrefs 概述 PlayerPrefs是Unity引擎内置的主要用来存储和读取玩家偏好设定的一个类这是官方手册里写的它的主要用途。但其实它不只可以存储玩家的偏好设定也可以用来存储简单的数据。
API及用法 #region 存储相关//PlayerPrefs的数据存储 类似于键值对存储 一个键对应一个值//提供了存储3种数据的方法 int float string//键: string类型 //值int float string 对应3种APIPlayerPrefs.SetInt(myAge, 26);PlayerPrefs.SetFloat(myHeight, 188.5f);PlayerPrefs.SetString(myName, Danny);//直接调用Set相关方法 只会把数据存到内存里//当游戏结束时 Unity会自动把数据存到硬盘中//如果游戏不是正常结束的 而是崩溃 数据是不会存到硬盘中的//只要调用该方法 就会马上存储到硬盘中PlayerPrefs.Save();//PlayerPrefs是有局限性的 它只能存3种类型的数据//如果你想要存储别的类型的数据 只能降低精度 或者上升精度来进行存储bool sex true;PlayerPrefs.SetInt(sex, sex ? 1 : 0);//如果不同类型用同一键名进行存储 会进行覆盖PlayerPrefs.SetFloat(myAge, 20.2f);#endregion#region 读取相关//注意 运行时 只要你Set了对应键值对//即使你没有马上存储Save在本地//也能够读取出信息//intint age PlayerPrefs.GetInt(myAge);print(age);//前提是 如果找不到myAge对应的值 就会返回函数的第二个参数 默认值age PlayerPrefs.GetInt(myAge, 100);print(age);//floatfloat height PlayerPrefs.GetFloat(myHeight, 1000f);print(height);//stringstring name PlayerPrefs.GetString(myName);print(name);//第二个参数 默认值 对于我们的作用//就是 在得到没有的数据的时候 就可以用它来进行基础数据的初始化//判断数据是否存在if( PlayerPrefs.HasKey(myName) ){print(存在myName对应的键值对数据);}#endregion#region 删除数据//删除指定键值对PlayerPrefs.DeleteKey(myAge);//删除所有存储的信息PlayerPrefs.DeleteAll();#endregion电脑中存放的位置
PlayerPrefs将数据存储在了电脑的注册表中。
打开方式 Win r 输入regedit进入注册表界面 HKEY_CURRENT_USER/Software/Unity 若你在编辑器中测试的就进入UnityEditor找到你工程的公司/工程名 文件夹 优缺点 优点 PlayerPrefs是数据持久化系列中最简单的一部分内容简单快捷易懂
缺点
重复工作繁多自定义数据类型都需要自己去实现读取功能而且代码的相似度极高数据容易被修改、只要找到文件位置就可以轻易的进行数据修改
主要用处
单独使用他的原生功能适合存储一些对安全性要求不高的简单数据但也不能小看它对他进行简单的封装也可以变的方便又安全
封装PlayerPrefs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;/// summary
/// PlayerPrefs数据管理类 统一管理数据的存储和读取
/// /summary
public class PlayerPrefsDataMgr
{private static PlayerPrefsDataMgr instance new PlayerPrefsDataMgr();public static PlayerPrefsDataMgr Instance{get{return instance;}}private PlayerPrefsDataMgr(){}/// summary/// 存储数据/// /summary/// param namedata数据对象/param/// param namekeyName数据对象的唯一key 自己控制/parampublic void SaveData( object data, string keyName ){//就是要通过 Type 得到传入数据对象的所有的 字段//然后结合 PlayerPrefs来进行存储#region 第一步 获取传入数据对象的所有字段Type dataType data.GetType();//得到所有的字段FieldInfo[] infos dataType.GetFields();#endregion#region 第二步 自己定义一个key的规则 进行数据存储//我们存储都是通过PlayerPrefs来进行存储的//保证key的唯一性 我们就需要自己定一个key的规则//我们自己定一个规则// keyName_数据类型_字段类型_字段名#endregion#region 第三步 遍历这些字段 进行数据存储string saveKeyName ;FieldInfo info;for (int i 0; i infos.Length; i){//对每一个字段 进行数据存储//得到具体的字段信息info infos[i];//通过FieldInfo可以直接获取到 字段的类型 和字段的名字//字段的类型 info.FieldType.Name//字段的名字 info.Name;//要根据我们定的key的拼接规则 来进行key的生成//Player1_PlayerInfo_Int32_agesaveKeyName keyName _ dataType.Name _ info.FieldType.Name _ info.Name;//现在得到了Key 按照我们的规则//接下来就要来通过PlayerPrefs来进行存储//如何获取值//info.GetValue(data)//封装了一个方法 专门来存储值 SaveValue(info.GetValue(data), saveKeyName);}PlayerPrefs.Save();#endregion}private void SaveValue(object value, string keyName){//直接通过PlayerPrefs来进行存储了//就是根据数据类型的不同 来决定使用哪一个API来进行存储//PlayerPrefs只支持3种类型存储 //判断 数据类型 是什么类型 然后调用具体的方法来存储Type fieldType value.GetType();//类型判断//是不是intif( fieldType typeof(int) ){//为int数据加密int rValue (int)value;rValue 10;PlayerPrefs.SetInt(keyName, rValue);}else if (fieldType typeof(float)){PlayerPrefs.SetFloat(keyName, (float)value);}else if (fieldType typeof(string)){PlayerPrefs.SetString(keyName, value.ToString());}else if (fieldType typeof(bool)){//自己顶一个存储bool的规则PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);}//如何判断 泛型类的类型呢//通过反射 判断 父子关系//这相当于是判断 字段是不是IList的子类else if( typeof(IList).IsAssignableFrom(fieldType) ){//父类装子类IList list value as IList;//先存储 数量 PlayerPrefs.SetInt(keyName, list.Count);int index 0;foreach (object obj in list){//存储具体的值SaveValue(obj, keyName index);index;}}//判断是不是Dictionary类型 通过Dictionary的父类来判断else if( typeof(IDictionary).IsAssignableFrom(fieldType) ){//父类装自来IDictionary dic value as IDictionary;//先存字典长度PlayerPrefs.SetInt(keyName, dic.Count);//遍历存储Dic里面的具体值//用于区分 表示的 区分 keyint index 0;foreach (object key in dic.Keys){SaveValue(key, keyName _key_ index);SaveValue(dic[key], keyName _value_ index);index;}}//基础数据类型都不是 那么可能就是自定义类型else{SaveData(value, keyName);}}/// summary/// 读取数据/// /summary/// param nametype想要读取数据的 数据类型Type/param/// param namekeyName数据对象的唯一key 自己控制/param/// returns/returnspublic object LoadData( Type type, string keyName ){//不用object对象传入 而使用 Type传入//主要目的是节约一行代码在外部//假设现在你要 读取一个Player类型的数据 如果是object 你就必须在外部new一个对象传入//现在有Type的 你只用传入 一个Type typeof(Player) 然后我在内部动态创建一个对象给你返回出来//达到了 让你在外部 少写一行代码的作用//根据你传入的类型 和 keyName//依据你存储数据时 key的拼接规则 来进行数据的获取赋值 返回出去//根据传入的Type 创建一个对象 用于存储数据object data Activator.CreateInstance(type);//要往这个new出来的对象中存储数据 填充数据//得到所有字段FieldInfo[] infos type.GetFields();//用于拼接key的字符串string loadKeyName ;//用于存储 单个字段信息的 对象FieldInfo info;for (int i 0; i infos.Length; i){info infos[i];//key的拼接规则 一定是和存储时一模一样 这样才能找到对应数据loadKeyName keyName _ type.Name _ info.FieldType.Name _ info.Name;//有key 就可以结合 PlayerPrefs来读取数据//填充数据到data中 info.SetValue(data, LoadValue(info.FieldType, loadKeyName));}return data;}/// summary/// 得到单个数据的方法/// /summary/// param namefieldType字段类型 用于判断 用哪个api来读取/param/// param namekeyName用于获取具体数据/param/// returns/returnsprivate object LoadValue(Type fieldType, string keyName){//根据 字段类型 来判断 用哪个API来读取if( fieldType typeof(int) ){//解密 减10return PlayerPrefs.GetInt(keyName, 0) - 10;}else if (fieldType typeof(float)){return PlayerPrefs.GetFloat(keyName, 0);}else if (fieldType typeof(string)){return PlayerPrefs.GetString(keyName, );}else if (fieldType typeof(bool)){//根据自定义存储bool的规则 来进行值的获取return PlayerPrefs.GetInt(keyName, 0) 1 ? true : false;}else if( typeof(IList).IsAssignableFrom(fieldType) ){//得到长度int count PlayerPrefs.GetInt(keyName, 0);//实例化一个List对象 来进行赋值//用了反射中双A中 Activator进行快速实例化List对象IList list Activator.CreateInstance(fieldType) as IList;for (int i 0; i count; i){//目的是要得到 List中泛型的类型 list.Add(LoadValue(fieldType.GetGenericArguments()[0], keyName i));}return list;}else if( typeof(IDictionary).IsAssignableFrom(fieldType) ){//得到字典的长度int count PlayerPrefs.GetInt(keyName, 0);//实例化一个字典对象 用父类装子类IDictionary dic Activator.CreateInstance(fieldType) as IDictionary;Type[] kvType fieldType.GetGenericArguments();for (int i 0; i count; i){dic.Add(LoadValue(kvType[0], keyName _key_ i),LoadValue(kvType[1], keyName _value_ i));}return dic;}else{return LoadData(fieldType, keyName);}}
}数据持久化之XML
XML是什么
全称可扩展性标记语言EXtensible Markup LanguageXML是国际通用的他是被设计来用于传输和存储数据的一种文本特殊格式文件名后缀一般为.xml
读取XML信息
C#读取XML的方法有几种
1.XmlDocument (把数据加载到内存中方便读取)2.XmlTextReader (以流形式加载内存占用更少但是是单向只读使用不是特别方便除非有特殊需求否则不会使用)3.Linq 使用XmlDocument类读取是较方便最容易理解和操作的方法
读取xml文件信息
XmlDocument xml new XmlDocument();
//通过XmlDocument读取xml文件 有两个API
//1.直接根据xml字符串内容 来加载xml文件
//存放在Resorces文件夹下的xml文件加载处理
TextAsset asset Resources.LoadTextAsset(TestXml);
print(asset.text);
//通过这个方法 就能够翻译字符串为xml对象
xml.LoadXml(asset.text);//2.是通过xml文件的路径去进行加载
xml.Load(Application.streamingAssetsPath /TestXml.xml); 读取元素和属性信息
//节点信息类
//XmlNode 单个节点信息类
//节点列表信息
//XmlNodeList 多个节点信息类//获取xml当中的根节点
XmlNode root xml.SelectSingleNode(Root);
//再通过根节点 去获取下面的子节点
XmlNode nodeName root.SelectSingleNode(name);
//如果想要获取节点包裹的元素信息 直接 .InnerText
print(nodeName.InnerText);XmlNode nodeAge root.SelectSingleNode(age);
print(nodeAge.InnerText);XmlNode nodeItem root.SelectSingleNode(Item);
//第一种方式 直接 中括号获取信息
print(nodeItem.Attributes[id].Value);
print(nodeItem.Attributes[num].Value);
//第二种方式
print(nodeItem.Attributes.GetNamedItem(id).Value);
print(nodeItem.Attributes.GetNamedItem(num).Value);//这里是获取 一个节点下的同名节点的方法
XmlNodeList friendList root.SelectNodes(Friend);//遍历方式一迭代器遍历
//foreach (XmlNode item in friendList)
//{
// print(item.SelectSingleNode(name).InnerText);
// print(item.SelectSingleNode(age).InnerText);
//}
//遍历方式二通过for循环遍历
//通过XmlNodeList中的 成员变量 Count可以得到 节点数量
for (int i 0; i friendList.Count; i)
{print(friendList[i].SelectSingleNode(name).InnerText);print(friendList[i].SelectSingleNode(age).InnerText);
}
总结
1.读取XML文件
XmlDocument xml new XmlDocument();读取文本方式1 xml.LoadXml(传入xml文本字符串)读取文本方式2 xml.Load(传入路径)
2.读取元素和属性
获取单个节点 : XmlNode node xml.SelectSingleNode(节点名)获取多个节点 : XmlNodeList nodeList xml.SelectNodes(节点名)获取节点元素内容node.InnerText获取节点元素属性 1.item.Attributes[属性名].Value 2.item.Attributes.GetNamedItem(属性名).Value
通过迭代器遍历或者循环遍历XmlNodeList对象 可以获取到各单个元素节点
写入XML信息
选择存储目录 注意存储xml文件 在Unity中一定是使用各平台都可读可写可找到的路径
Resources 可读 不可写 打包后找不到 × Application.streamingAssetsPath 可读 PC端可写 找得到 × Application.dataPath 打包后找不到 × Application.persistentDataPath 可读可写找得到 √
string path Application.persistentDataPath /TextXml.xml;
print(Application.persistentDataPath);
存储xml文件
关键类 XmlDocument 用于创建节点 存储文件关键类 XmlDeclaration 用于添加版本信息关键类 XmlElement 节点类
存储有5步
//1.创建文本对象
XmlDocument xml new XmlDocument();//2.添加固定版本信息
//这一句代码 相当于就是创建?xml version1.0 encodingUTF-8?这句内容
XmlDeclaration xmlDec xml.CreateXmlDeclaration(1.0, UTF-8, );
//创建完成过后 要添加进入 文本对象中
xml.AppendChild(xmlDec);//3.添加根节点
XmlElement root xml.CreateElement(Root);
xml.AppendChild(root);//4.为根节点添加子节点
//加了一个 name子节点
XmlElement name xml.CreateElement(name);
name.InnerText Danny;
root.AppendChild(name);XmlElement atk xml.CreateElement(atk);
atk.InnerText 10;
root.AppendChild(atk);XmlElement listInt xml.CreateElement(listInt);
for (int i 1; i 3; i)
{XmlElement childNode xml.CreateElement(int);childNode.InnerText i.ToString();listInt.AppendChild(childNode);
}
root.AppendChild(listInt);XmlElement itemList xml.CreateElement(itemList);
for (int i 1; i 3; i)
{XmlElement childNode xml.CreateElement(Item);//添加属性childNode.SetAttribute(id, i.ToString());childNode.SetAttribute(num, (i * 10).ToString());itemList.AppendChild(childNode);
}
root.AppendChild(itemList);//5.保存
xml.Save(path);
修改xml文件
//1.先判断是否存在文件
if( File.Exists(path) )
{//2.加载后 直接添加节点 移除节点即可XmlDocument newXml new XmlDocument();newXml.Load(path);//修改就是在原有文件基础上 去移除 或者添加//移除XmlNode node;// newXml.SelectSingleNode(Root).SelectSingleNode(atk);//这种是一种简便写法 通过/来区分父子关系node newXml.SelectSingleNode(Root/atk);//得到自己的父节点XmlNode root2 newXml.SelectSingleNode(Root);//移除子节点方法root2.RemoveChild(node);//添加节点XmlElement speed newXml.CreateElement(moveSpeed);speed.InnerText 20;root2.AppendChild(speed);//改了记得存newXml.Save(path);
}
总结
1.路径选取 在运行过程中存储 只能往可写且能找到的文件夹存储 故 选择了Application.persistentDataPath
2.存储xml关键类 XmlDocument 文件 创建节点 CreateElement 创建固定内容方法 CreateXmlDeclaration 添加节点 AppendChild 保存 Save XmlDeclaration 版本 XmlElement 元素节点 设置属性方法SetAttribute
3.修改 RemoveChild移除节点
XML序列化
什么是序列化和反序列化
序列化把对象转化为可传输的字节序列过程称为序列化序列化就是把想要存储的内容转换为字节序列用于存储或传递反序列化把字节序列还原为对象的过程称为反序列化反序列化就是把存储或收到的字节序列信息解析读取出来使用
xml序列化
//1.第一步准备一个数据结构类
Lesson1Test lt new Lesson1Test();
//2.进行序列化
// 关键知识点
// XmlSerializer 用于序列化对象为xml的关键类
// StreamWriter 用于存储文件
// using 用于方便流对象释放和销毁//第一步确定存储路径
string path Application.persistentDataPath /Lesson1Test.xml;
print(Application.persistentDataPath);
//第二步结合 using知识点 和 StreamWriter这个流对象 来写入文件
// 括号内的代码写入一个文件流 如果有该文件 直接打开并修改 如果没有该文件 直接新建一个文件
// using 的新用法 括号当中包裹的声明的对象 会在 大括号语句块结束后 自动释放掉
// 当语句块结束 会自动帮助我们调用 对象的 Dispose这个方法 让其进行销毁
// using一般都是配合 内存占用比较大 或者 有读写操作时 进行使用的
using ( StreamWriter stream new StreamWriter(path) )
{//第三步进行xml文件序列化XmlSerializer s new XmlSerializer(typeof(Lesson1Test));//这句代码的含义 就是通过序列化对象 对我们类对象进行翻译 将其翻译成我们的xml文件 写入到对应的文件中//第一个参数 文件流对象 //第二个参数: 想要备翻译 的对象//注意 翻译机器的类型 一定要和传入的对象是一致的 不然会报错s.Serialize(stream, lt);
}
自定义节点名 或 设置属性 //可以通过特性 设置节点或者设置属性 并且修改名字
[XmlArray(IntList)]
[XmlArrayItem(Int32)]
public Listint listInt;[XmlElement(testPublic123123)]
public int testPublic;[XmlAttribute(Test1)]
public int test1 1;
总结
序列化流程
有一个想要保存的类对象使用XmlSerializer 序列化该对象通过StreamWriter 配合 using将数据存储 写入文件
注意
只能序列化公共成员不支持字典序列化可以通过特性修改节点信息 或者设置属性信息Stream相关要配合using使用
XML反序列化
#region 知识点一 判断文件是否存在
string path Application.persistentDataPath /Lesson1Test.xml;
if( File.Exists(path) )
{#region 知识点二 反序列化//关键知识// 1.using 和 StreamReader// 2.XmlSerializer 的 Deserialize反序列化方法//读取文件using (StreamReader reader new StreamReader(path)){//产生了一个 序列化反序列化的翻译机器XmlSerializer s new XmlSerializer(typeof(Lesson1Test));Lesson1Test lt s.Deserialize(reader) as Lesson1Test;}#endregion
}
#endregion#region 总结
//1.判断文件是否存在 File.Exists
//2.文件流获取 StreamReader reader new StreamReader(path)
//3.根据文件流 XmlSerializer通过Deserialize反序列化 出对象//注意List对象 如果有默认值 反序列化时 不会清空 会往后面添加
#endregion
自定义类继承IXmlSerializable
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using UnityEngine;public class TestLesson3 : IXmlSerializable
{public int test1;public string test2;//返回结构public XmlSchema GetSchema(){return null;}//反序列化时 会自动调用的方法public void ReadXml(XmlReader reader){//在里面可以自定义反序列化 的规则//1.读属性//this.test1 int.Parse(reader[test1]);//this.test2 reader[test2];//2.读节点//方式一//reader.Read();//这时是读到的test1节点//reader.Read();//这时是读到的test1节点包裹的内容//this.test1 int.Parse(reader.Value);//得到当前内容的值//reader.Read();//这时读到的是尾部包裹节点//reader.Read();//这时是读到的test2节点//reader.Read();//这时是读到的test2节点包裹的内容//this.test2 reader.Value;//方式二//while(reader.Read())//{// if( reader.NodeType XmlNodeType.Element )// {// switch (reader.Name)// {// case test1:// reader.Read();// this.test1 int.Parse(reader.Value);// break;// case test2:// reader.Read();// this.test2 reader.Value;// break;// }// }//}//3.读包裹元素节点XmlSerializer s new XmlSerializer(typeof(int));XmlSerializer s2 new XmlSerializer(typeof(string));//跳过根节点reader.Read();reader.ReadStartElement(test1);test1 (int)s.Deserialize(reader);reader.ReadEndElement();reader.ReadStartElement(test2);test2 s2.Deserialize(reader).ToString();reader.ReadEndElement();}//序列化时 会自动调用的方法public void WriteXml(XmlWriter writer){//在里面可以自定义序列化 的规则//如果要自定义 序列化的规则 一定会用到 XmlWriter中的一些方法 来进行序列化//1.写属性//writer.WriteAttributeString(test1, this.test1.ToString());//writer.WriteAttributeString(test2, this.test2);//2.写节点//writer.WriteElementString(test1, this.test1.ToString());//writer.WriteElementString(test2, this.test2);//3.写包裹节点XmlSerializer s new XmlSerializer(typeof(int));writer.WriteStartElement(test1);s.Serialize(writer, test1);writer.WriteEndElement();XmlSerializer s2 new XmlSerializer(typeof(string));writer.WriteStartElement(test2);s2.Serialize(writer, test2);writer.WriteEndElement();}
}public class Lesson3 : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){#region 知识点一 IXmlSerializable是什么//C# 的XmlSerializer 提供了可拓展内容 //可以让一些不能被序列化和反序列化的特殊类能被处理//让特殊类继承 IXmlSerializable 接口 实现其中的方法即可#endregion#region 知识点二 自定义类实践TestLesson3 t new TestLesson3();t.test2 123;string path Application.persistentDataPath /TestLesson3.xml;//序列化using (StreamWriter writer new StreamWriter(path)){//序列化翻译机器XmlSerializer s new XmlSerializer(typeof(TestLesson3));//在序列化时 如果对象中的引用成员 为空 那么xml里面是看不到该字段的s.Serialize(writer, t);}//反序列化using (StreamReader reader new StreamReader(path)){//序列化翻译机器XmlSerializer s new XmlSerializer(typeof(TestLesson3));TestLesson3 t2 s.Deserialize(reader) as TestLesson3;}#endregion}// Update is called once per framevoid Update(){}
}扩展Dictionary支持序列号反序列化
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using UnityEngine;public class SerizlizerDictionaryTKey, TValue : DictionaryTKey, TValue, IXmlSerializable
{public XmlSchema GetSchema(){return null;}//自定义字典的 反序列化 规则public void ReadXml(XmlReader reader){XmlSerializer keySer new XmlSerializer(typeof(TKey));XmlSerializer valueSer new XmlSerializer(typeof(TValue));//要跳过根节点reader.Read();//判断 当前不是元素节点 结束 就进行 反序列化while (reader.NodeType ! XmlNodeType.EndElement){//反序列化键TKey key (TKey)keySer.Deserialize(reader);//反序列化值TValue value (TValue)valueSer.Deserialize(reader);//存储到字典中this.Add(key, value);}}//自定义 字典的 序列化 规则public void WriteXml(XmlWriter writer){XmlSerializer keySer new XmlSerializer(typeof(TKey));XmlSerializer valueSer new XmlSerializer(typeof(TValue));foreach (KeyValuePairTKey, TValue kv in this){//键值对 的序列化keySer.Serialize(writer, kv.Key);valueSer.Serialize(writer, kv.Value);}}
}封装
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;public class XmlDataMgr
{private static XmlDataMgr instance new XmlDataMgr();public static XmlDataMgr Instance instance;private XmlDataMgr() { }/// summary/// 保存数据到xml文件中/// /summary/// param namedata数据对象/param/// param namefileName文件名/parampublic void SaveData(object data, string fileName){//1.得到存储路径string path Application.persistentDataPath / fileName .xml;//2.存储文件using(StreamWriter writer new StreamWriter(path)){//3.序列化XmlSerializer s new XmlSerializer(data.GetType());s.Serialize(writer, data);}}/// summary/// 从xml文件中读取内容 /// /summary/// param nametype对象类型/param/// param namefileName文件名/param/// returns/returnspublic object LoadData(Type type, string fileName){//1。首先要判断文件是否存在string path Application.persistentDataPath / fileName .xml;if( !File.Exists(path) ){path Application.streamingAssetsPath / fileName .xml;if (!File.Exists(path)){//如果根本不存在文件 两个路径都找过了//那么直接new 一个对象 返回给外部 无非 里面都是默认值return Activator.CreateInstance(type);}}//2.存在就读取using (StreamReader reader new StreamReader(path)){//3.反序列化 取出数据XmlSerializer s new XmlSerializer(type);return s.Deserialize(reader);}}}优缺点
优点
XML是国际通用的规则跨平台游戏、软件、网页等等都可以用文件结构浅显易懂非常容易编辑和理解可以用于网络通讯进行交换数据
缺点
重复工作繁多且代码相似度极高自定义数据类型都需要自己去实现存储和读取的功能数据容易被修改只要找到位置就可以轻易修改数据
主要用处 网络游戏
可以用于存储一些简单的不重要的数据可以用于传输信息基本不会大范围使用因为比较耗流量 单机游戏
用于存储游戏相关数据用于配置游戏数据 数据持久化之Json
Json是什么 JSON: JavaScript Object Notation JS对象简谱 , 是一种轻量级的数据交换格式. 主要在网络通讯中用于传输数据或本地数据存储和读取在游戏中可以把游戏数据按照Json格式标准存储在Json文档中再将Json文档存储在硬盘上或者传输给远端达到数据持久化或者数据传输的目的 易于人阅读和编写同时也易于机器解析和生成并有效的提高网络传输效率
Json和Xml的异同 共同点 都是纯文本 都有层级结构 都具有描述性 不同点 Json配置更简单 Json在某种情况下读写更快 JsonUnity JsonUnity是Unity自带的公共类 将内存中对象序列化为Json格式的字符串 将Json字符串反序列化为类对象 在文件中存读字符串 1.存储字符串到指定路径文件中 第一个参数 填写的是 存储的路径 第二个参数 填写的是 存储的字符串内容 注意第一个参数 必须是存在的文件路径 如果没有对应文件夹 会报错 File.WriteAllText(Application.persistentDataPath /Test.json, json文件);print(Application.persistentDataPath); 2.在指定路径文件中读取字符串 string str File.ReadAllText(Application.persistentDataPath /Test.json);print(str);
JsonUnity序列化 序列化把内存中的数据 存储到硬盘上 方法JsonUtility.ToJson(对象)
public class MrZhong
{public string name;public int age;public bool sex;public float testF;public double testD;public int[] ids;public Listint ids2;public Dictionaryint, string dic;public Dictionarystring, string dic2;public Student s1;public ListStudent s2s;[SerializeField]private int privateI 1;[SerializeField]protected int protectedI 2;
} MrZhong t new MrZhong();t.name Danny;t.age 18;t.sex false;t.testF 1.4f;t.testD 1.4;t.ids new int[] { 1, 2, 3, 4 };t.ids2 new Listint() { 1, 2, 3 };t.dic new Dictionaryint, string() { { 1, 123 }, { 2, 234 } };t.dic2 new Dictionarystring, string() { { 1, 123 }, { 2, 234 } };t.s1 null;//new Student(1, 小红);t.s2s new ListStudent() { new Student(2, 小明), new Student(3, 小强) };//Jsonutility提供了线程的方法 可以把类对象 序列化为 json字符串string jsonStr JsonUtility.ToJson(t);File.WriteAllText(Application.persistentDataPath /MrZhong.json, jsonStr);
注意 float序列化时看起来会有一些误差 自定义类需要加上序列化特性[System.Serializable] 想要序列化私有变量 需要加上特性[SerializeField] JsonUtility不支持字典 JsonUtlity存储null对象不会是null 而是默认值的数据
JsonUtlity进行反序列化 反序列化把硬盘上的数据 读取到内存中 方法 JsonUtility.FromJson(字符串)
//读取文件中的 Json字符串
jsonStr File.ReadAllText(Application.persistentDataPath /MrZhong.json);
//使用Json字符串内容 转换成类对象
MrZhong t2 JsonUtility.FromJson(jsonStr, typeof(MrZhong)) as MrZhong;
//推荐这种写法
MrZhong t3 JsonUtility.FromJsonMrZhong(jsonStr); 注意 如果Json中数据少了读取到内存中类对象中时不会报错 注意事项 //1.JsonUtlity无法直接读取数据集合
public class RoleData
{public ListRoleInfo list;
}
[System.Serializable]
public class RoleInfo
{public int hp;public int speed;public int volume;public string resName;public int scale;
}[System.Serializable]
public class RoleInfo
{public int hp;public int speed;public int volume;public string resName;public int scale;
}
jsonStr File.ReadAllText(Application.streamingAssetsPath /RoleInfo2.json);
print(jsonStr);
//ListRoleInfo roleInfoList JsonUtility.FromJsonListRoleInfo(jsonStr);
RoleData data JsonUtility.FromJsonRoleData(jsonStr);//2.文本编码格式需要时UTF-8 不然无法加载 总结
必备知识点 —— File存读字符串的方法 ReadAllText和WriteAllTextJsonUtlity提供的序列化反序列化方法 ToJson 和 FromJson自定义类需要加上序列化特性[System.Serializable]私有保护成员 需要加上[SerializeField]JsonUtlity不支持字典JsonUtlity不能直接将数据反序列化为数据集合Json文档编码格式必须是UTF-8
LitJson LitJson它是一个第三方库用于处理Json的序列化和反序列化 LitJson是C#编写的体积小、速度快、易于使用 它可以很容易的嵌入到我们的代码中 只需要将LitJson代码拷贝到工程中即可 获取LitJson 1.前往LitJson官网 2.通过官网前往GitHub获取最新版本代码 3.讲代码拷贝到Unity工程中 即可开始使用LitJson
使用LitJson进行序列化 方法JsonMapper.ToJson(对象)
Danny t new Danny();
t.name Danny;
t.age 18;
t.sex true;
t.testF 1.4f;
t.testD 1.4;t.ids new int[] { 1, 2, 3, 4 };
t.ids2 new Listint() { 1, 2, 3 };
//t.dic new Dictionaryint, string() { { 1, 123 }, { 2, 234 } };
t.dic2 new Dictionarystring, string() { { 1, 123 }, { 2, 234 } };t.s1 null;//new Student(1, 小红);
t.s2s new ListStudent2() { new Student2(2, 小明), new Student2(3, 小强) };string jsonStr JsonMapper.ToJson(t);
print(Application.persistentDataPath);
File.WriteAllText(Application.persistentDataPath /Danny.json, jsonStr); 注意 相对JsonUtlity不需要加特性 不能序列化私有变量 支持字典类型,字典的键 建议都是字符串 因为 Json的特点 Json中的键会加上双引号 需要引用LitJson命名空间 LitJson可以准确的保存null类型
使用LitJson反序列化 方法JsonMapper.ToObject(字符串)
jsonStr File.ReadAllText(Application.persistentDataPath /Danny.json);
//JsonData是LitJson提供的类对象 可以用键值对的形式去访问其中的内容
JsonData data JsonMapper.ToObject(jsonStr);
print(data[name]);
print(data[age]);
//通过泛型转换 更加的方便 建议使用这种方式
Danny t JsonMapper.ToObjectDanny(jsonStr); 注意 类结构需要无参构造函数否则反序列化时报错 字典虽然支持 但是键在使用为数值时会有问题 需要使用字符串类型
注意事项
//1.LitJson可以直接读取数据集合
jsonStr File.ReadAllText(Application.streamingAssetsPath /RoleInfo.json);
RoleInfo2[] arr JsonMapper.ToObjectRoleInfo2[](jsonStr);ListRoleInfo2 list JsonMapper.ToObjectListRoleInfo2(jsonStr);jsonStr File.ReadAllText(Application.streamingAssetsPath /Dic.json);
Dictionarystring, int dicTest JsonMapper.ToObjectDictionarystring, int(jsonStr);//2.文本编码格式需要是UTF-8 不然无法加载
总结 LitJson提供的序列化反序列化方法 JsonMapper.ToJson和ToObject LitJson无需加特性 LitJson不支持私有变量 LitJson支持字典序列化反序列化 LitJson可以直接将数据反序列化为数据集合 LitJson反序列化时 自定义类型需要无参构造 Json文档编码格式必须是UTF-8
封装
using LitJson;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;/// summary
/// 序列化和反序列化Json时 使用的是哪种方案
/// /summary
public enum JsonType
{JsonUtlity,LitJson,
}/// summary
/// Json数据管理类 主要用于进行 Json的序列化存储到硬盘 和 反序列化从硬盘中读取到内存中
/// /summary
public class JsonMgr
{private static JsonMgr instance new JsonMgr();public static JsonMgr Instance instance;private JsonMgr() { }//存储Json数据 序列化public void SaveData(object data, string fileName, JsonType type JsonType.LitJson){//确定存储路径string path Application.persistentDataPath / fileName .json;//序列化 得到Json字符串string jsonStr ;switch (type){case JsonType.JsonUtlity:jsonStr JsonUtility.ToJson(data);break;case JsonType.LitJson:jsonStr JsonMapper.ToJson(data);break;}//把序列化的Json字符串 存储到指定路径的文件中File.WriteAllText(path, jsonStr);}//读取指定文件中的 Json数据 反序列化public T LoadDataT(string fileName, JsonType type JsonType.LitJson) where T : new(){//确定从哪个路径读取//首先先判断 默认数据文件夹中是否有我们想要的数据 如果有 就从中获取string path Application.streamingAssetsPath / fileName .json;//先判断 是否存在这个文件//如果不存在默认文件 就从 读写文件夹中去寻找if(!File.Exists(path))path Application.persistentDataPath / fileName .json;//如果读写文件夹中都还没有 那就返回一个默认对象if (!File.Exists(path))return new T();//进行反序列化string jsonStr File.ReadAllText(path);//数据对象T data default(T);switch (type){case JsonType.JsonUtlity:data JsonUtility.FromJsonT(jsonStr);break;case JsonType.LitJson:data JsonMapper.ToObjectT(jsonStr);break;}//把对象返回出去return data;}
}数据持久化之2进制 2进制是什么 2进制是计算机技术中广泛采用的一种数制。2进制数据是用0和1两个数码来表示的数。他的基数是2进位规则是“逢二进一”。 计算机中存储的数据本质上都是2进制数的存储在计算机中位bit是最小的存储单位。1位就是一个0或者一个1。 也就是说一个文件的数据本质上都是由n个0和1组合而成的通过不同的解析规则最终呈现在我们眼前。 数据持久化之2进制的好处 通过2进制进行数据持久化的好处 安全性较高 效率较高 为网络通信做铺垫
各类型数据转字节数据 回顾C#知识——不同变量类型
有符号 sbyte int short long无符号 byte uint ushort ulong浮点 float double decimal特殊 bool char string 回顾C#知识——变量的本质 变量的本质是2进制 在内存中都以字节的形式存储着 1byte 8bit 1bit(位)不是0就是1 通过sizeof方法可以看到常用变量类型占用的字节空间长度
print(有符号);
print(sbyte sizeof(sbyte) 字节);//1字节
print(int sizeof(int) 字节);//4字节
print(short sizeof(short) 字节);//2字节
print(long sizeof(long) 字节);//8字节
print(无符号);
print(byte sizeof(byte) 字节);//1字节
print(uint sizeof(uint) 字节);//4字节
print(ushort sizeof(ushort) 字节);//2字节
print(ulong sizeof(ulong) 字节);//8字节
print(浮点);
print(float sizeof(float) 字节);//4字节
print(double sizeof(double) 字节);//8字节
print(decimal sizeof(decimal) 字节);//16字节
print(特殊);
print(bool sizeof(bool) 字节);//1字节
print(char sizeof(char) 字节);//2字节 2进制文件读写的本质
它就是通过将各类型变量转换为字节数组将字节数组直接存储到文件中一般人是看不懂存储的数据的不仅可以节约存储空间提升效率还可以提升安全性而且在网络通信中我们直接传输的数据也是字节数据2进制数据 数据和字节数据相互转换 C#提供了一个公共类帮助我们进行转化 我们只需要记住API即可 类名BitConverter 命名空间using System
//1.将各类型转字节
byte[] bytes BitConverter.GetBytes(256);//2.字节数组转各类型
int i BitConverter.ToInt32(bytes, 0);
print(i); 标准编码格式 编码是用预先规定的方法将文字、数字或其它对象编成数码或将信息、数据转换成规定的电脉冲信号。 为保证编码的正确性编码要规范化、标准化即需有标准的编码格式。 常见的编码格式有ASCII、ANSI、GBK、GB2312、UTF - 8、GB18030和UNICODE等。 计算机中数据的本质就是2进制数据 编码格式就是用对应的2进制数 对应不同的文字 由于世界上有各种不同的语言所有会有很多种不同的编码格式 不同的编码格式 对应的规则是不同的 如果在读取字符时采用了不统一的编码格式可能会出现乱码 游戏开发中常用编码格式 UTF-8 中文相关编码格式 GBK 在C#中有一个专门的编码格式类 来帮助我们将字符串和字节数组进行转换 类名Encoding需要引用命名空间using System.Text;
//1.将字符串以指定编码格式转字节
byte[] bytes2 Encoding.UTF8.GetBytes(Danny);//2.字节数组以指定编码格式转字符串
string s Encoding.UTF8.GetString(bytes2);
print(s);
总结我们可以通过BitConverter和Encoding类,将所有C#提供给我们的数据类型和字节数组之间进行相互转换了
文件操作相关 代码中的文件操作是做什么 在电脑上我们可以在操作系统中创建删除修改文件可以增删查改各种各样的文件类型代码中的文件操作就是通过代码来做这些事情 文件相关操作公共类 C#提供了一个名为File文件的公共类 让我们可以快捷的通过代码操作文件相关 类名File命名空间 System.IO 文件操作File类的常用内容 1.判断文件是否存在
if(File.Exists(Application.dataPath /UnityTeach.zhong))
{print(文件存在);
}
else
{print(文件不存在);
} 2.创建文件
FileStream fs File.Create(Application.dataPath /UnityTeach.zhong); 3.写入文件
//将指定字节数组 写入到指定路径的文件中
byte[] bytes BitConverter.GetBytes(999);
File.WriteAllBytes(Application.dataPath /UnityTeach.zhong, bytes);//将指定的string数组内容 一行行写入到指定路径中
string[] strs new string[] { 123, Danny, 123123kdjfsalk, 123123123125243};
File.WriteAllLines(Application.dataPath /UnityTeach2.zhong, strs);//将指定字符串写入指定路径
File.WriteAllText(Application.dataPath /UnityTeach3.zhong, Danny666\n哈哈哈哈123123131231241234123); 4.读取文件
//读取字节数据
bytes File.ReadAllBytes(Application.dataPath /UnityTeach.zhong);
print(BitConverter.ToInt32(bytes, 0));//读取所有行信息
strs File.ReadAllLines(Application.dataPath /UnityTeach2.zhong);
for (int i 0; i strs.Length; i)
{print(strs[i]);
}//读取所有文本信息
print(File.ReadAllText(Application.dataPath /UnityTeach3.zhong)); 5.删除文件
//注意 如果删除打开着的文件 会报错
File.Delete(Application.dataPath /UnityTeach.zhong); 6.复制文件
//参数一现有文件 需要是流关闭状态
//参数二目标文件
File.Copy(Application.dataPath /UnityTeach2.zhong, Application.dataPath /Danny.tanglaoshi, true); 7.文件替换
//参数一用来替换的路径
//参数二被替换的路径
//参数三备份路径
File.Replace(Application.dataPath /UnityTeach3.zhong, Application.dataPath /Danny.tanglaoshi, Application.dataPath /Danny备份.tanglaoshi); 8.以流的形式 打开文件并写入或读取
//参数一路径
//参数二打开模式
//参数三访问模式
FileStream fs File.Open(Application.dataPath /UnityTeach2.zhong, FileMode.OpenOrCreate, FileAccess.ReadWrite);
文件操作相关文件流 什么是文件流 在C#中提供了一个文件流类 FileStream类它主要作用是用于读写文件的细节我们之前学过的File只能整体读写文件而FileStream可以以读写字节的形式处理文件 也就是说文件里面存储的数据就像是一条数据流数组或者列表我们可以通过FileStream一部分一部分的读写数据流。比如我可以先存一个int4个字节再存一个bool1个字节再存一个stringn个字节利用FileStream可以以流式逐个读写。 FileStream文件流类常用方法 类名FileStream引用命名空间System.IO 1.打开或创建指定文件 方法一new FileStream 参数一路径 参数二打开模式 CreateNew:创建新文件 如果文件存在 则报错 Create:创建文件如果文件存在 则覆盖 Open:打开文件如果文件不存在 报错 OpenOrCreate:打开或者创建文件根据实际情况操作 Append:若存在文件则打开并查找文件尾或者创建一个新文件 Truncate:打开并清空文件内容 参数三访问模式 参数四共享权限 None 谢绝共享 Read 允许别的程序读取当前文件 Write 允许别的程序写入该文件 ReadWrite 允许别的程序读写该文件
FileStream fs new FileStream(Application.dataPath /Lesson3.Danny, FileMode.Create, FileAccess.ReadWrite); 方法二File.Create 参数一路径 参数二缓存大小 参数三描述如何创建或覆盖该文件不常用 Asynchronous 可用于异步读写 DeleteOnClose 不在使用时自动删除 Encrypted 加密 None 不应用其它选项 RandomAccess 随机访问文件 SequentialScan 从头到尾顺序访问文件 WriteThrough 通过中间缓存直接写入磁盘
FileStream fs2 File.Create(Application.dataPath /Lesson3.Danny); 方法三File.Open 参数一路径 参数二打开模式
FileStream fs3 File.Open(Application.dataPath /Lesson3.Danny, FileMode.Open); 2.重要属性和方法
FileStream fs File.Open(Application.dataPath Lesson3.Danny, FileMode.OpenOrCreate);
//文本字节长度
print(fs.Length);//是否可读
if( fs.CanRead )
{print(文件可读);
}//是否可写
if( fs.CanWrite )
{print(文件可写);}//将字节写入文件 当写入后 一定执行一次
fs.Flush();//关闭流 当文件读写完毕后 一定执行
fs.Close();//缓存资源销毁回收
fs.Dispose(); 3.写入字节
print(Application.persistentDataPath);
using (FileStream fs new FileStream(Application.persistentDataPath /Lesson3.Danny, FileMode.OpenOrCreate, FileAccess.Write))
{byte[] bytes BitConverter.GetBytes(999);//方法Write//参数一写入的字节数组//参数二数组中的开始索引//参数三写入多少个字节fs.Write(bytes, 0, bytes.Length);//写入字符串时bytes Encoding.UTF8.GetBytes(Danny哈哈哈哈);//先写入长度//int length bytes.Length;fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);//再写入字符串具体内容fs.Write(bytes, 0, bytes.Length);//避免数据丢失 一定写入后要执行的方法fs.Flush();//销毁缓存 释放资源fs.Dispose();
} 4.读取字节 方法一挨个读取字节数组
using (FileStream fs2 File.Open(Application.persistentDataPath /Lesson3.Danny, FileMode.Open, FileAccess.Read))
{//读取第一个整形byte[] bytes2 new byte[4];//参数一用于存储读取的字节数组的容器//参数二容器中开始的位置//参数三读取多少个字节装入容器//返回值当前流索引前进了几个位置int index fs2.Read(bytes2, 0, 4);int i BitConverter.ToInt32(bytes2, 0);print(取出来的第一个整数 i);//999print(索引向前移动 index 个位置);//读取第二个字符串//读取字符串字节数组长度index fs2.Read(bytes2, 0, 4);print(索引向前移动 index 个位置);int length BitConverter.ToInt32(bytes2, 0);//要根据我们存储的字符串字节数组的长度 来声明一个新的字节数组 用来装载读取出来的数据bytes2 new byte[length];index fs2.Read(bytes2, 0, length);print(索引向前移动 index 个位置);//得到最终的字符串 打印出来print(Encoding.UTF8.GetString(bytes2));fs2.Dispose();
} 方法二一次性读取再挨个读取
using (FileStream fs3 File.Open(Application.persistentDataPath /Lesson3.Danny, FileMode.Open, FileAccess.Read))
{//一开始就申明一个 和文件字节数组长度一样的容器byte[] bytes3 new byte[fs3.Length];fs3.Read(bytes3, 0, (int)fs3.Length);fs3.Dispose();//读取整数print(BitConverter.ToInt32(bytes3, 0));//得去字符串字节数组的长度int length2 BitConverter.ToInt32(bytes3, 4);//得到字符串print(Encoding.UTF8.GetString(bytes3, 8, length2));
}更加安全的使用文件流对象 using关键字重要用法 using (申明一个引用对象) { 使用对象 } 无论发生什么情况 当using语句块结束后 ,会自动调用该对象的销毁方法 避免忘记销毁或关闭流,using是一种更安全的使用方法 强调 目前我们对文件流进行操作 为了文件操作安全 都用using来进行处理最好 总结 通过FIleStream读写时一定要注意 读的规则一定是要和写是一致的 我们存储数据的先后顺序是我们制定的规则 只要按照规则读写就能保证数据的正确性
文件夹相关操作 C#提供给我们的文件夹操作公共类 类名:Directory命名空间using System.IO 1.判断文件夹是否存在
if( Directory.Exists(Application.dataPath /数据持久化四))
{print(存在文件夹);
}
else
{print(文件夹不存在);
} 2.创建文件夹
DirectoryInfo info Directory.CreateDirectory(Application.dataPath /数据持久化四); 3.删除文件夹
//参数一路径
//参数二是否删除非空目录如果为true将删除整个目录如果是false仅当该目录为空时才可删除
Directory.Delete(Application.dataPath /数据持久化四); 4.查找文件夹和文件
//得到指定路径下所有文件夹名
string[] strs Directory.GetDirectories(Application.dataPath);
for (int i 0; i strs.Length; i)
{print(strs[i]);
}//得到指定路径下所有文件名
strs Directory.GetFiles(Application.dataPath);
for (int i 0; i strs.Length; i)
{print(strs[i]);
} 5.移动文件夹
//如果第二个参数所在的路径 已经存在了一个文件夹 那么会报错
//移动会把文件夹中的所有内容一起移到新的路径
Directory.Move(Application.dataPath /数据持久化四, Application.dataPath /123123123); DirectoryInfo和FileInfo DirectoryInfo目录信息类我们可以通过它获取文件夹的更多信息它主要出现在两个地方 1.创建文件夹方法的返回值
DirectoryInfo dInfo Directory.CreateDirectory(Application.dataPath /数据持久化123);
//全路径
print(dInfo.FullName);
//文件名
print(dInfo.Name); 2.查找上级文件夹信息
DirectoryInfo dInfo Directory.GetParent(Application.dataPath /数据持久化123);
//全路径
print(dInfo.FullName);
//文件名
print(dInfo.Name);//重要方法
//得到所有子文件夹的目录信息
DirectoryInfo[] dInfos dInfo.GetDirectories();//FileInfo文件信息类
//我们可以通过DirectoryInfo得到该文件下的所有文件信息
FileInfo[] fInfos dInfo.GetFiles();
for (int i 0; i fInfos.Length; i)
{print(**************);print(fInfos[i].Name);//文件名print(fInfos[i].FullName);//路径print(fInfos[i].Length);//字节长度print(fInfos[i].Extension);//后缀名
}
c#类对象的序列化 序列化类对象第一步—申明类对象 注意如果要使用C#自带的序列化2进制方法申明类时需要添加[System.Serializable]特性 序列化类对象第二步—将对象进行2进制序列化 方法一使用内存流得到2进制字节数组 主要用于得到字节数组 可以用于网络传输 新知识点 1.内存流对象 类名MemoryStream 命名空间System.IO 2.2进制格式化对象 类名BinaryFormatter 命名空间System.Runtime.Serialization.Formatters.Binary、 通过其中的序列化方法即可进行序列化生成字节数组 主要方法序列化方法 Serialize
//测试用例
[System.Serializable]
public class Person
{public int age 1;public string name Danny;public int[] ints new int[] { 1, 2, 3, 4, 5 };public Listint list new Listint() { 1, 2, 3, 4 };public Dictionaryint, string dic new Dictionaryint, string() { { 1,123},{ 2,1223},{ 3,435345 } };public StructTest st new StructTest(2, 123);public ClssTest ct new ClssTest();
}[System.Serializable]
public struct StructTest
{public int i;public string s;public StructTest(int i, string s){this.i i;this.s s;}
}[System.Serializable]
public class ClssTest
{public int i 1;
}
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 /Lesson5.Danny, bytes);//关闭内存流ms.Close();
}方法二使用文件流进行存储 主要用于存储到文件中
using (FileStream fs new FileStream(Application.dataPath /Lesson5_2.Danny, FileMode.OpenOrCreate, FileAccess.Write))
{//2进制格式化程序BinaryFormatter bf new BinaryFormatter();//序列化对象 生成2进制字节数组 写入到内存流当中bf.Serialize(fs, p);fs.Flush();fs.Close();
} c#类对象的反序列化 反序列化之 反序列化文件中数据 主要类 FileStream文件流类 BinaryFormatter 2进制格式化类 主要方法 Deserizlize 通过文件流打开指定的2进制数据文件
using (FileStream fs File.Open(Application.dataPath /Lesson5_2.Danny, FileMode.Open, FileAccess.Read))
{//申明一个 2进制格式化类BinaryFormatter bf new BinaryFormatter();//反序列化Person p bf.Deserialize(fs) as Person;fs.Close();
} 反序列化之 反序列化网络传输过来的2进制数据 主要类 MemoryStream内存流类 BinaryFormatter 2进制格式化类 主要方法 Deserizlize ,目前没有网络传输 我们还是直接从文件中获取
byte[] bytes File.ReadAllBytes(Application.dataPath /Lesson5_2.Danny);
//申明内存流对象 一开始就把字节数组传输进去
using (MemoryStream ms new MemoryStream(bytes))
{//申明一个 2进制格式化类BinaryFormatter bf new BinaryFormatter();//反序列化Person p bf.Deserialize(ms) as Person;ms.Close();
}
c#2进制数据加密 何时加密何时解密 当我们将类对象转换为2进制数据时进行加密 当我们将2进制数据转换为类对象时进行解密 加密是否是100%安全 定记住加密只是提高破解门槛没有100%保密的数据通过各种尝试始终是可以破解加密规则的只是时间问题加密只能起到提升一定的安全性 常用加密算法 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 /Lesson7.Danny, bytes);
}//解密
byte[] bytes2 File.ReadAllBytes(Application.dataPath /Lesson7.Danny);
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();
} 优点 效率高 节约空间 安全性高 缺点 可读性差 无法从数据文件看懂数据或修改数据 主要用处 网络游戏 可以用于存储客户端数据 可以用于传输信息 单机游戏 用于储存游戏相关数据 用于配置游戏数据
Unity添加菜单栏按钮 为编辑器菜单栏添加新的选项入口 以通过Unity提供我们的MenuItem特性在菜单栏添加选项按钮 特性名MenuItem 命名空间UnityEditor 规则一一定是静态方法 规则二我们这个菜单栏按钮 必须有至少一个子路径斜杠 不然会报错 它不支持只有一个菜单栏入口 规则三这个特性可以用在任意的类当中
[MenuItem(GameTool/Danny)]
private static void Danny()
{Debug.Log(测试测试);//刷新Project窗口内容//类名AssetDatabase//命名空间UnityEditor//方法RefreshDirectory.CreateDirectory(Application.dataPath /Danny);//刷新资源管理器AssetDatabase.Refresh();#endregion
} Editor文件夹可以放在项目的任何文件夹下可以有多个,放在其中的内容项目打包时不会被打包到项目中,一般编辑器相关代码都可以放在该文件夹中
Excel DLL包的导入 Excel表本质上也是一堆数据只不过它有自己的存储读取规则如果我们想要通过代码读取它那么必须知道它的存储规则 官网是专门提供了对应的DLL文件用来解析Excel文件的 Dll文件库文件你可以理解为它是许多代码的集合将相关代码集合在库文件中可以方便迁移和使用有了某个DLL文件我们就可以使用其中已经写好的代码 而Excel的DLL包就是官方已经把解析Excel表的相关类和方法写好了方便用户直接使用 导入官方提供的Excel相关DLL文件将DLL文件放置在Editor文件夹下
Excel数据读取 打开Excel表 主要知识点 FileStream读取文件流 IExcelDataReader类从流中读取Excel数据 DataSet 数据集合类 将Excel数据转存进其中方便读取
[MenuItem(GameTool/打开Excel表)]
private static void OpenExcel()
{using (FileStream fs File.Open(Application.dataPath /ArtRes/Excel/PlayerInfo.xlsx, FileMode.Open, FileAccess.Read )){//通过我们的文件流获取Excel数据IExcelDataReader excelReader ExcelReaderFactory.CreateOpenXmlReader(fs);//将excel表中的数据转换为DataSet数据类型 方便我们 获取其中的内容DataSet result excelReader.AsDataSet();//得到Excel文件中的所有表信息for (int i 0; i result.Tables.Count; i){Debug.Log(表名 result.Tables[i].TableName);Debug.Log(行数 result.Tables[i].Rows.Count);Debug.Log(列数 result.Tables[i].Columns.Count);}fs.Close();}
} 获取Excel表中单元格的信息 主要知识点 FileStream读取文件流 IExcelDataReader类从流中读取Excel数据 DataSet 数据集合类 将Excel数据转存进其中方便读取 DataTable 数据表类 表示Excel文件中的一个表 DataRow 数据行类 表示某张表中的一行数据
[MenuItem(GameTool/读取Excel里的具体信息)]
private static void ReadExcel()
{using (FileStream fs File.Open(Application.dataPath /ArtRes/Excel/PlayerInfo.xlsx, FileMode.Open, FileAccess.Read)){IExcelDataReader excelReader ExcelReaderFactory.CreateOpenXmlReader(fs);DataSet result excelReader.AsDataSet();for (int i 0; i result.Tables.Count; i){//得到其中一张表的具体数据DataTable table result.Tables[i];//得到其中一行的数据//DataRow row table.Rows[0];//得到行中某一列的信息//Debug.Log(row[1].ToString());DataRow row;for (int j 0; j table.Rows.Count; j){//得到每一行的信息row table.Rows[j];Debug.Log(*********新的一行************);for (int k 0; k table.Columns.Count; k){Debug.Log(row[k].ToString());}}}fs.Close();}
}
封装 ExcelTool
using Excel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;public class ExcelTool
{/// summary/// excel文件存放的路径/// /summarypublic static string EXCEL_PATH Application.dataPath /ArtRes/Excel/;/// summary/// 数据结构类脚本存储位置路径/// /summarypublic static string DATA_CLASS_PATH Application.dataPath /Scripts/ExcelData/DataClass/;/// summary/// 容器类脚本存储位置路径/// /summarypublic static string DATA_CONTAINER_PATH Application.dataPath /Scripts/ExcelData/Container/;/// summary/// 真正内容开始的行号/// /summarypublic static int BEGIN_INDEX 4;[MenuItem(GameTool/GenerateExcel)]private static void GenerateExcelInfo(){//记在指定路径中的所有Excel文件 用于生成对应的3个文件DirectoryInfo dInfo Directory.CreateDirectory(EXCEL_PATH);//得到指定路径中的所有文件信息 相当于就是得到所有的Excel表FileInfo[] files dInfo.GetFiles();//数据表容器DataTableCollection tableConllection;for (int i 0; i files.Length; i){//如果不是excel文件就不要处理了if (files[i].Extension ! .xlsx files[i].Extension ! .xls)continue;//打开一个Excel文件得到其中的所有表的数据using (FileStream fs files[i].Open(FileMode.Open, FileAccess.Read)){IExcelDataReader excelReader ExcelReaderFactory.CreateOpenXmlReader(fs);tableConllection excelReader.AsDataSet().Tables;fs.Close();}//遍历文件中的所有表的信息foreach (DataTable table in tableConllection){//生成数据结构类GenerateExcelDataClass(table);//生成容器类GenerateExcelContainer(table);//生成2进制数据GenerateExcelBinary(table);}}}/// summary/// 生成Excel表对应的数据结构类/// /summary/// param nametable/paramprivate static void GenerateExcelDataClass(DataTable table){//字段名行DataRow rowName GetVariableNameRow(table);//字段类型行DataRow rowType GetVariableTypeRow(table);//判断路径是否存在 没有的话 就创建文件夹if (!Directory.Exists(DATA_CLASS_PATH))Directory.CreateDirectory(DATA_CLASS_PATH);//如果我们要生成对应的数据结构类脚本 其实就是通过代码进行字符串拼接 然后存进文件就行了string str public class table.TableName \n{\n;//变量进行字符串拼接for (int i 0; i table.Columns.Count; i){str public rowType[i].ToString() rowName[i].ToString() ;\n;}str };//把拼接好的字符串存到指定文件中去File.WriteAllText(DATA_CLASS_PATH table.TableName .cs, str);//刷新Project窗口AssetDatabase.Refresh();}/// summary/// 生成Excel表对应的数据容器类/// /summary/// param nametable/paramprivate static void GenerateExcelContainer(DataTable table){//得到主键索引int keyIndex GetKeyIndex(table);//得到字段类型行DataRow rowType GetVariableTypeRow(table);//没有路径创建路径if (!Directory.Exists(DATA_CONTAINER_PATH))Directory.CreateDirectory(DATA_CONTAINER_PATH);string str using System.Collections.Generic;\n;str public class table.TableName Container \n{\n;str ;str public Dictionary rowType[keyIndex].ToString() , table.TableName ;str dataDic new Dictionary rowType[keyIndex].ToString() , table.TableName ();\n;str };File.WriteAllText(DATA_CONTAINER_PATH table.TableName Container.cs, str);//刷新Project窗口AssetDatabase.Refresh();}/// summary/// 生成excel2进制数据/// /summary/// param nametable/paramprivate static void GenerateExcelBinary(DataTable table){//没有路径创建路径if (!Directory.Exists(BinaryDataMgr.DATA_BINARY_PATH))Directory.CreateDirectory(BinaryDataMgr.DATA_BINARY_PATH);//创建一个2进制文件进行写入using (FileStream fs new FileStream(BinaryDataMgr.DATA_BINARY_PATH table.TableName .ExcelData, FileMode.OpenOrCreate, FileAccess.Write)){//存储具体的excel对应的2进制信息//1.先要存储我们需要写多少行的数据 方便我们读取//-4的原因是因为 前面4行是配置规则 并不是我们需要记录的数据内容fs.Write(BitConverter.GetBytes(table.Rows.Count - 4), 0, 4);//2.存储主键的变量名string keyName GetVariableNameRow(table)[GetKeyIndex(table)].ToString();byte[] bytes Encoding.UTF8.GetBytes(keyName);//存储字符串字节数组的长度fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);//存储字符串字节数组fs.Write(bytes, 0, bytes.Length);//遍历所有内容的行 进行2进制的写入DataRow row;//得到类型行 根据类型来决定应该如何写入数据DataRow rowType GetVariableTypeRow(table);for (int i BEGIN_INDEX; i table.Rows.Count; i){//得到一行的数据row table.Rows[i];for (int j 0; j table.Columns.Count; j){switch (rowType[j].ToString()){case int:fs.Write(BitConverter.GetBytes(int.Parse(row[j].ToString())), 0, 4);break;case float:fs.Write(BitConverter.GetBytes(float.Parse(row[j].ToString())), 0, 4);break;case bool:fs.Write(BitConverter.GetBytes(bool.Parse(row[j].ToString())), 0, 1);break;case string:bytes Encoding.UTF8.GetBytes(row[j].ToString());//写入字符串字节数组的长度fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);//写入字符串字节数组fs.Write(bytes, 0, bytes.Length);break;}}}fs.Close();}AssetDatabase.Refresh();}/// summary/// 获取变量名所在行/// /summary/// param nametable/param/// returns/returnsprivate static DataRow GetVariableNameRow(DataTable table){return table.Rows[0];}/// summary/// 获取变量类型所在行/// /summary/// param nametable/param/// returns/returnsprivate static DataRow GetVariableTypeRow(DataTable table){return table.Rows[1];}/// summary/// 获取主键索引/// /summary/// param nametable/param/// returns/returnsprivate static int GetKeyIndex(DataTable table){DataRow row table.Rows[2];for (int i 0; i table.Columns.Count; i){if (row[i].ToString() key)return i;}return 0;}
}BinaryDataMgr
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;/// summary
/// 2进制数据管理器
/// /summary
public class BinaryDataMgr
{/// summary/// 2进制数据存储位置路径/// /summarypublic static string DATA_BINARY_PATH Application.streamingAssetsPath /Binary/;/// summary/// 用于存储所有Excel表数据的容器/// /summaryprivate Dictionarystring, object tableDic new Dictionarystring, object();/// summary/// 数据存储的位置/// /summaryprivate static string SAVE_PATH Application.persistentDataPath /Data/;private static BinaryDataMgr instance new BinaryDataMgr();public static BinaryDataMgr Instance instance;private BinaryDataMgr(){}public void InitData(){LoadTablePlayerInfoContainer, PlayerInfo();}/// summary/// 加载Excel表的2进制数据到内存中 /// /summary/// typeparam nameT容器类名/typeparam/// typeparam nameK数据结构类类名/typeparampublic void LoadTableT,K(){//读取 excel表对应的2进制文件 来进行解析using (FileStream fs File.Open(DATA_BINARY_PATH typeof(K).Name .ExcelData, FileMode.Open, FileAccess.Read)){byte[] bytes new byte[fs.Length];fs.Read(bytes, 0, bytes.Length);fs.Close();//用于记录当前读取了多少字节了int index 0;//读取多少行数据int count BitConverter.ToInt32(bytes, index);index 4;//读取主键的名字int keyNameLength BitConverter.ToInt32(bytes, index);index 4;string keyName Encoding.UTF8.GetString(bytes, index, keyNameLength);index keyNameLength;//创建容器类对象Type contaninerType typeof(T);object contaninerObj Activator.CreateInstance(contaninerType);//得到数据结构类的TypeType classType typeof(K);//通过反射 得到数据结构类 所有字段的信息FieldInfo[] infos classType.GetFields();//读取每一行的信息for (int i 0; i count; i){//实例化一个数据结构类 对象object dataObj Activator.CreateInstance(classType);foreach (FieldInfo info in infos){if( info.FieldType typeof(int) ){//相当于就是把2进制数据转为int 然后赋值给了对应的字段info.SetValue(dataObj, BitConverter.ToInt32(bytes, index));index 4;}else if (info.FieldType typeof(float)){info.SetValue(dataObj, BitConverter.ToSingle(bytes, index));index 4;}else if (info.FieldType typeof(bool)){info.SetValue(dataObj, BitConverter.ToBoolean(bytes, index));index 1;}else if (info.FieldType typeof(string)){//读取字符串字节数组的长度int length BitConverter.ToInt32(bytes, index);index 4;info.SetValue(dataObj, Encoding.UTF8.GetString(bytes, index, length));index length;}}//读取完一行的数据了 应该把这个数据添加到容器对象中//得到容器对象中的 字典对象object dicObject contaninerType.GetField(dataDic).GetValue(contaninerObj);//通过字典对象得到其中的 Add方法MethodInfo mInfo dicObject.GetType().GetMethod(Add);//得到数据结构类对象中 指定主键字段的值object keyValue classType.GetField(keyName).GetValue(dataObj);mInfo.Invoke(dicObject, new object[] { keyValue, dataObj });}//把读取完的表记录下来tableDic.Add(typeof(T).Name, contaninerObj);fs.Close();}}/// summary/// 得到一张表的信息/// /summary/// typeparam nameT容器类名/typeparam/// returns/returnspublic T GetTableT() where T:class{string tableName typeof(T).Name;if (tableDic.ContainsKey(tableName))return tableDic[tableName] as T;return null;}/// summary/// 存储类对象数据/// /summary/// param nameobj/param/// param namefileName/parampublic void Save(object obj, string fileName){//先判断路径文件夹有没有if (!Directory.Exists(SAVE_PATH))Directory.CreateDirectory(SAVE_PATH);using (FileStream fs new FileStream(SAVE_PATH fileName .ExcelData, FileMode.OpenOrCreate, FileAccess.Write)){BinaryFormatter bf new BinaryFormatter();bf.Serialize(fs, obj);fs.Close();}}/// summary/// 读取2进制数据转换成对象/// /summary/// typeparam nameT/typeparam/// param namefileName/param/// returns/returnspublic T LoadT(string fileName) where T:class{//如果不存在这个文件 就直接返回泛型对象的默认值if( !File.Exists(SAVE_PATH fileName .ExcelData) )return default(T);T obj;using (FileStream fs File.Open(SAVE_PATH fileName .ExcelData, FileMode.Open, FileAccess.Read)){BinaryFormatter bf new BinaryFormatter();obj bf.Deserialize(fs) as T;fs.Close();}return obj;}
}结尾
以上是本人在项目中及平时积累的知识归纳总结
如果喜欢我的文章欢迎关注、点赞、转发、评论大家的支持是我最大的动力
如有疑问或BUG请加入QQ群一起交流探讨
技术交流QQ群:1011399921