- 省流:本文包含Unity数据持久化Xml相关的用法,主要内容分为三部分:XML基础语法与在C#中的操作、XML序列化与反序列化(含自定义类及字典的序列化),最终将封装一个单例模式的XML数据管理器
Unity数据持久化_XML
- 一、XML基础语法与在C#中的操作
-
- [1.1 XML的基础语法](#1.1 XML的基础语法)
- 1.2 XML在C#中的使用
- 二、序列化与反序列化
-
- [2.1 XML序列化](#2.1 XML序列化)
- [2.2 XML反序列化](#2.2 XML反序列化)
- [2.3 IXmlSerializable接口 自定义序列化](#2.3 IXmlSerializable接口 自定义序列化)
- 三、单例模式实现XML数据管理器
-
- [3.1 字典的序列化与反序列化](#3.1 字典的序列化与反序列化)
- [3.2 读取与存储功能](#3.2 读取与存储功能)
- [3.3 问题](#3.3 问题)
一、XML基础语法与在C#中的操作
1.1 XML的基础语法
(1)XML基础
XML是一种可扩展语言,他是一种树形结构
XML是纯文本的,机器和人都可读,可以用来存储数据
(2)XML的基本语法固定内容:
<?xml version="1.0" encoding="UTF-8"?>
固定内容为XML的版本 和 编码格式,其必须写在XML文件中的第一行XML注释:
<!--注释内容-->XML节点:
<节点名>节点内容</节点名>
在XML文本中必须包含一个根节点XML属性:
<节点名 属性名="属性内容">节点内容<节点名 属性名="属性内容">
XML属性的具体内容必须用单引号或双引号包裹,无论其是什么类型
若节点内容无意义,可简写为:<节点名 属性名="属性内容"/>注意:在XML文件的编写时,XML对字母大小写和空格的检测 十分严格,因此在完成一个XML文本时,你可以通过一下流程检查你的XML文本的语法
菜鸟教程 > XML教程 > XML验证器
1.2 XML在C#中的使用
(1)xml文件的存放位置
- 对于只读不写的XML文件存放在Resoursces 或 StreamingAssets文件夹
- 动态存储的XML文件存放在Application.presistentPath
(2)C#读取XML的方法(a)读取XML中的信息
- 第一步:创建一个用来存储XML的数据结构
XmlDocument x = new XmlDocument()- 第二步:读取XML中的信息
法一:根据XML字符串内容读取XML中的信息(需要先加载对应的xml文j件)
法二:通过xml文件路径读取xml变量实例.Load(Application.StrmingAssetsPath + "target.xml")
(b)通过XML信息 读取 元素 和 属性 信息
- 获取根节点:
XmlNode root = xml变量实例.SelectSingleNode(string 根节点的名字)- 获取对应节点的子节点:
XmlNod resoultXmlNode = targetXmlNode.SelectSingleNode(string 目标节点的名字)- 获取节点的具体内容:
Xml节点实例.InnerText- 获取节点的属性信息:
法一:Xml节点实例.Attributes[属性的键].Value
法二:Xml节点实例.Attributes.GetNamedItem(属性的键).Value- 获取节点下的多个同名子节点
XmlNodeList list =Xml节点实例.SelectNodes(目标节点名)
(后续可以通过遍历来找到具体的节点)
(3)C#存储XML的方法(a)xml文件存储时用到的类
- XmlDocument 用于创建节点
- XmlDeclaration 存储xml文件的版本信息
- XmlElement 是xml的节点类
(b)xml文件存储的流程
- 第一步:创建文本对象XmlDocument,用来存储XML
XmlDocument playerInfo = new XmlDocument()- 第二步:为xml添加 固定信息
创建固定信息 并保存到XmlDeclaration类型变量中
XmlDeclaration versionInfo = playerInfo.CreateXmlDeclaration("1.0", "UTF-8", "")
将固定信息添加到xml文件中
XmlDocument实例.AppendChild(versionInfo)- 第三步:为文本对象添加节点
必须有一个根节点
- 创建根节点并添加到xml文件中(即用来存储xml的XmlDocument 实例)
XmlElement 根节点实例 = XmlDocument实例.CreateElement("根节点名")
XmlDocument实例.AppendChild(根节点实例)- 添加子节点(playerInfo为XmlDocument实例,root为根节点实例)
name.InnerTest是在设置 子节点的信息内容- 添加属性
- 第四步保存:
XmlDocument实例.Save("保存路径")========== 【演示:完整的保存流程】 ==========
csharp
//(3)xml文件存储的流程
// 第一步:创建文本对象
XmlDocument playerInfo = new XmlDocument();
// 第二步:为xml文本对象添加 固定信息
// 创建固定信息 并保存到XmlDeclaration类型变量中
XmlDeclaration versionInfo = playerInfo.CreateXmlDeclaration("1.0", "UTF-8", "");
// 将固定信息添加到xml文本中
playerInfo.AppendChild(versionInfo);
// 第三步:为文本对象添加根节点
// 创建根节点
XmlElement root = playerInfo.CreateElement("root");
// 将根节点添加到xml文本文件中
playerInfo.AppendChild(root);
// 第四步:添加子节点
XmlElement name = playerInfo.CreateElement("name");
name.InnerText = "包子";//子节点的信息内容
root.AppendChild(name);//将子节点添加到根节点
// 第四步:添加属性
XmlElement item = playerInfo.CreateElement("item");
item.SetAttribute("道具名", "包子");
item.SetAttribute("数量", "10");
item.SetAttribute("效果", "恢复十点生命");
name.AppendChild(item);
// 第五步:保存
playerInfo.Save(path);
// 检测是否保存成功
if(File.Exists(path))
{
print("保存成功 路径为:" + path);
//打开该路径下的文件
Application.OpenURL(Path.GetDirectoryName(path));
// 删除xml文件的某一属性
// 第一步,找到要删除的子节点
// playerInfo.SelectSingleNode(target);
// 第二步,从该子节点的根节点处将其删除
// root.RemoveChild(要移除的目标节点)
}
(c)删除子节点和建成是否保存成功
- 删除xml文件的某一属性
第一步,找到要删除的子节点
XmlDocument实例.SelectSingleNode(target)
第二步,从该子节点的根节点处将其删除
目标节点的根节点.RemoveChild(要移除的目标节点)- 检测是否保存成功
File.Exists(保存路径)========== 【演示:删除子节点和建成是否保存成功】 ==========
csharp
// 检测是否保存成功
if(File.Exists(path))
{
print("保存成功 路径为:" + path);
//打开该路径下的文件
Application.OpenURL(Path.GetDirectoryName(path));
// 删除xml文件的某一属性
// 第一步,找到要删除的子节点
// playerInfo.SelectSingleNode(target);
// 第二步,从该子节点的根节点处将其删除
// root.RemoveChild(要移除的目标节点)
}
二、序列化与反序列化
2.1 XML序列化
(1)什么是序列化和反序列化
序列化:将内存中的对象转化为可传输的字节序列(即XML文件)的过程,存储对象时使用
反序列化:将字节序列XML还原为对象实例的过程
(2)xml序列化使用前提类的访问权限为 public,且存在无参构造
类中的属性/字段 的访问权限为 public
只读属性无法被序列化
对于引用类型变量,当器为null时将不会对其序列化
(3)XML文件序列化的流程第一步:确定存储路径
string path = Application.persistentDataPath + "/playerInfo.xml"第二步:创建一个流写入器StreamWriter,并传入写入文件的保存的路径
StreamWriter stream = new StreamWriter(path)
注意:这里需要用using 包裹,作用是当path中存在对应文件时,则在对应文件中写入数据,不存在就先创建后写入
此外:using括号当中包裹的声明的对象 会在 大括号语句块结束后 自动释放掉
using (StreamWriter stream = new StreamWriter(path)){ using语句块 }第三步创建序列化器(第三四步位于 using语句块中)
XmlSerializer s = new XmlSerializer(typeof(序列化对象的类型))第四步:通过序列化器将具体实例对象序列化
s.Serialize(stream,序列化的实例对象)
你可以通过Application.OpenURL(Path.GetDirectoryName(path))快速找到保存的XML文件========== 【演示:将TestClass的实例对象序列化】 ==========
csharp
//(3)序列化的流程
// 创建的TestClass 实列对象
TestClass t = new TestClass();
//第一步:确定存储路径
string path = Application.persistentDataPath + "/playerInfo.xml";
// 第二步:创建一个流写入器StreamWriter,并传入写入文件的保存的路径
// 注意:这里需要用using 包裹,作用是当path中存在对应文件时,则在对应文件中写入数据,不存在就先创建后写入
// 此外:using括号当中包裹的声明的对象 会在 大括号语句块结束后 自动释放掉
using (StreamWriter stream = new StreamWriter(path))
{
//第三步:创建序列化器
XmlSerializer s = new XmlSerializer(typeof(TestClass));
//第四步:通过序列化器将具体实例对象序列化
s.Serialize(stream, t);
Application.OpenURL(Path.GetDirectoryName(path));
}
2.2 XML反序列化
(1)反序列化的流程
- 第一步:判断XML文件是否存在
File.Exists(path)
返回值为true时则存在,可以进行反序列化- 第二步:创建一个读取器,并传入要读取的地址
using (StreamReader reader = new StreamReader(path)){ using 语句块 }- 第三步:创建反序列化器
XmlSerializer s = new XmlSerializer(typeof(TestClass))- 第四步:通过反序列化器将XML文件还原为实列对象
TestClass t2 = s.Deserialize(reader) as TestClass========== 【演示:将XML文件反序列化TestClass实例对象】 ==========
csharp
// 反序列化:将字节序列还原为对象的过程
//(1)反序列化的流程
//- 第一步:判断XML文件是否存在
//读取XML文件的路径
string path = Application.persistentDataPath + "/playerInfo.xml";
if(File.Exists(path))
{
//- 第二步:创建一个读取器,并传入要读取的地址
using (StreamReader reader = new StreamReader(path))
{
//- 第三步:创建反序列化器,
XmlSerializer s = new XmlSerializer(typeof(TestClass));
//- 第四步:通过反序列化器将XML文件还原为实列对象
TestClass t2 = s.Deserialize(reader) as TestClass;
}
}
2.3 IXmlSerializable接口 自定义序列化
(1)继承IXmlSerializable接口的类要实现三个方法
(a) public XmlSchema GetSchema() 方法
该方法会返回一个结构,实现时只需要令其返回null即可
(b) public void WriteXml(XmlWriter writer) 方法该方法需要传入一个XmlWriter写入器,自定义类序列化时自动执行,用于自定义序列化规则
- 属性的序列化
writer.WriteAttributeString("属性名", 对应的值);- 节点的序列化
writer.WriteElementString("节点名", 对应的值);- 子节点的序列化
writer.WriteStartElement("根节点名")
进行子节点序列化
writer.WriteEndElement()========== 【演示:实现一个自定义类的序列化方法】 ==========
(c) public void ReadXml(XmlReader reader) 方法该方法需要传入一个XmlReader读取器,自定义类反序列化时自动执行,用于自定义反序列化规则
- 属性的反序列化
reader.GetAttribute("属性名")- 节点的反序列化
reader.ReadElementString("节点名")- 子节点的反序列化
reader.ReadStartElement("根节点名")
进行子节点反序列化
reader.ReadEndElement()========== 【演示:实现一个自定义类的反序列化方法】 ==========
三、单例模式实现XML数据管理器
3.1 字典的序列化与反序列化
XML实现数据管理依赖于将XML进行序列化,但对于一些数据结构,如字典,不能通过Serializable进行序列化,因此需要通过IXmlSerializable接口 自定义序列化
(1) 首先,要让字典实现序列化,需要继承IXmlSerializable接口,并实现其中的三个方法
但我们无法直接修改字典,所以我们需要创建一个自定义的可序列化的字典类
public class SerizlizerDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable(2) 接下来我们要实现其中的三个方法
- public void WriteXml(XmlWriter writer) 方法
只需要让他返回null即可- public void WriteXml(XmlWriter writer)
该方法用来实现自定义的可序列化的字典类的 反序列化功能首先,我们需要两个序列化器,用来分别序列化字典的 键 和 值
XmlSerializer keySer = new XmlSerializer(typeof(TKey))
XmlSerializer valueSer = new XmlSerializer(typeof(TValue))其次,我们遍历字典得到他的键值对item
foreach (KeyValuePair<TKey, TValue> item in this){ 遍历语句块 }接下来,我们通过写入器writer对键值进行写入,其中每一个键值对的键和值都会被item节点包裹(方便区分各键值对)该部分是遍历语句块中的内容
writer.WriteStartElement("item")
keySer.Serialize(writer, item.Key)
valueSer.Serialize(writer, item.Value)
writer.WriteEndElement()
- public void ReadXml(XmlReader reader)方法
该方法用来实现自定义的可序列化的字典类的 反序列化功能首先,我们需要两个反序列化器,用来分别反序列化字典的 键 和 值
XmlSerializer keySer = new XmlSerializer(typeof(TKey))
XmlSerializer valueSer = new XmlSerializer(typeof(TValue))其次,因为读取XML时,会先进入根节点,所以我们要读取根节点
reader.Read()接下来,我们通过读取器reader来进行读取键值对
while (reader.IsStartElement("item")){ while语句块 } 这里只会读取节点为item的节点,存在错误,详情看 3.3
reader.ReadEndElement()最后,将对应的键值存储到字典中 (该部分是while语句块中的内容)
TKey key = (TKey)keySer.Deserialize(reader)
TValue value = (TValue)valueSer.Deserialize(reader)
this.Add(key, value)
3.1完整代码
csharp
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using UnityEngine;
/// <summary>
/// 该脚本实现字典的序列化,因为我们无法修改字典,所以只能通过继承的方式实现一个可以序列化的字典
/// </summary>
/// <typeparam name="TKey">键的泛型</typeparam>
/// <typeparam name="TValue">值的泛型</typeparam>
public class SerizlizerDictionary<TKey, TValue> : Dictionary<TKey, 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.IsStartElement("item"))//这里会导致只读取item节点,详情看问题
{
//读取 Key
TKey key = (TKey)keySer.Deserialize(reader);
// 读取 Value
TValue value = (TValue)valueSer.Deserialize(reader);
// 添加到字典
this.Add(key, value);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
XmlSerializer keySer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSer = new XmlSerializer(typeof(TValue));
foreach (KeyValuePair<TKey, TValue> item in this)
{
//键值对 的序列化
//每一个键值对都的键值都是其子节点,用来区分
writer.WriteStartElement("item");
keySer.Serialize(writer, item.Key);
valueSer.Serialize(writer, item.Value);
//writer.WriteAttributeString(kv.Key.ToString(),kv.Value.ToString());
writer.WriteEndElement();
}
}
}
3.2 读取与存储功能
- 读取
首先检查读取的XML文件是否存在,存在则通过反序列化进行读取- 存储
首先,通过传入的文件名,设置保存路径
其次,进行序列化,将数据实例对象转化为XML文件
3.2完整代码
csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
public class XmlDataMgr
{
#region 单例模式实现 保证场景上只有一个实例
private static XmlDataMgr instance = new XmlDataMgr();
public static XmlDataMgr Instance => instance;
private XmlDataMgr() { }
#endregion
public void SaveData(object data, string fileName)
{
//通过传入的文件名,设置保存路径
string path = Application.persistentDataPath + "/" + fileName + ".xml";
//进行序列化,将数据实例对象转化为XML文件
using (StreamWriter writer = new StreamWriter(path))
{
XmlSerializer s = new XmlSerializer(data.GetType());
s.Serialize(writer, data);
}
}
public object LoadData(Type type, string fileName)
{
//反序列化,首先检查是否存在对应的XML文件
string path = Application.persistentDataPath + "/" + fileName + ".xml";
if (File.Exists(path))
{
using (StreamReader reader = new StreamReader(path))
{
//反序列化 取出数据
XmlSerializer s = new XmlSerializer(type);
return s.Deserialize(reader);
}
}
return null;
}
}
3.3 问题
在测试时,我发现可以正常将实例化对象序列化为XML文件
但是在读取时,出现错误
原因是,在我的原代码中只会读取 节点名为 item的节点,不会继续读他的子节点
修改后的代码为
csharp
public void ReadXml(XmlReader reader)
{
XmlSerializer keySer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSer = new XmlSerializer(typeof(TValue));
//要跳过根节点
reader.Read();
//当前元素为键值对就进行读取
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
//读取 Key
TKey key = (TKey)keySer.Deserialize(reader);
// 读取 Value
TValue value = (TValue)valueSer.Deserialize(reader);
// 添加到字典
this.Add(key, value);
reader.ReadEndElement();
}
}
测试结果为







