Unity复习学习随笔(六):XML数据

目录

XML是什么

XML基本语法

固定内容

基本规则

XML属性

XML读取

读取Xml信息

读取元素和属性信息

C#存储修改Xml文件

1.决定存储在哪个文件夹下

2.存储xml文件

3.修改xml文件

XML主要用处

XML序列化

什么是序列化和反序列化

xml序列化流程

自定义节点名或设置属性

XML反序列化

IXmlSerializable接口

IXmlSerializable是什么?

如何让Dictionary支持序列化反序列化

创建一个属于自己的XML文件管理类


XML是什么

全称:可拓展标记语言(Extensible Markup Language)

XML是国际通用的

它是被设计来用于传输和存储数据的一种文本特殊格式,文件后缀一般为.xml

XML基本语法

固定内容

固定语法:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>

version代表版本 encoding代表编码格式

所谓编码格式,就是读取文件时,解析字符串使用的编码是什么

编码格式

不同的字符,在内存中的二进制是不一样的,每一个字符对应一个数字

不同的编码格式,字符对应的二进制是不一样的

xml的基本语法就是**<元素标签></元素标签>**配对出现的

注:一定必须有一个根节点

节点之间可以填写数据,或者在其中包裹别的节点

基本规则

每个元素都必须有关闭标签

元素命名规则基本遵照c#中变量名命名规则

XML标签对大小写敏感

XML文件必须有根元素

特殊的符号应该用实体引用:

&lt ------ <小于

&gt ------ >大于

&amp ------ &和号

&apos ------ ' 单引号

&quot ------ " 引号

XML属性

属性就是在元素标签后面空格 添加的内容

注:属性必须用引号包裹,单引号双引号都可以

如果使用属性记录信息,不想使用元素记录,可以像下面一样写

XML 复制代码
<People name = "father" age = '50' />

属性和元素节点的区别

属性和元素节点只是写法上的区别而已,我们可以选择自己喜欢的方式来记录数据

XML文件存放位置

1.只读不写的XML文件,可以放在Resources或者StreamingAssets文件夹下

2.动态存储的XML文件,可以放在Application.persistentDataPath路径下

XML读取

C#读取XML的方法有几种

1:XmlDocument(把数据加载到内存中,方便读取)

2:XmlTextReader(以流形式加载,内存占用更少,但是是单向只读,使用不是特别方便,除非有特殊需求,否则不会使用)

3:Linq(等以后涉及到Linq时再详细说明)

使用XmlDocument类读取是比较方便容易理解何操作的方法

读取Xml信息

1.直接根据xml字符串内容,来加载xml文件

存放在Resources文件夹下的xml文件加载处理

cs 复制代码
XmlDocument xml = new XmlDocument();

TextAsset asset = Resources.Load<TextAsset>("TestXml");
//通过这个方法就能够翻译字符串为xml对象
xml.LoadXml(asset.text);

2.通过xml文件的路径去进行加载

cs 复制代码
xml.Load(Application.streamingAssetsPath + "/TestXml.xml");

读取元素和属性信息

节点信息类:XmlNode 单个节点信息类

节点列表信息:XmlNodeList 多个节点信息类

获取xml当中的根节点:

cs 复制代码
XmlNode root = xml.SelectSingleNode("Root");

再通过根节点,去获取下面的子节点:

cs 复制代码
XmlNode nodeName = root.SelectSingleNode("name");

如果想要获取节点包裹的元素信息,直接 .InnerText

cs 复制代码
print(nodeName.InnerText);

获取属性:

cs 复制代码
XmlNode nodeImte = root.SelectSingleNode("Item");
print(nodeItem.Attributes["id"].Value);
print(nodeItem.Attributes["num"].Value);

第二种获取属性方式:

cs 复制代码
print(nodeItem.Attributes.GetNamedItem("id").Value);

如果一个节点下有同名节点:

cs 复制代码
XmlNodeList friendList = root.SelectNodes("Friend");

遍历方式:

迭代器:

cs 复制代码
foreach(XmlNode item in friendList)
{
    print(item.selectSingleNode("name").InnerText);
}

通过for循环:

cs 复制代码
for(int i = 0; i< friendList.Count;i++)
{
    print(friendList[i].SelectSingleNode("name").InnerText);
}

练习

C#存储修改Xml文件

1.决定存储在哪个文件夹下

注意:存储xml文件,在Unity中一定时使用各平台都可读可写可找到的路径

1.Resources:可读不可写,打包后找不到

2.Application.streamingAssetsPath 可读,pc端可写,找得到

3.Application.dataPath 打包后找不到

4.Application.persistentDataPath 可读可写找的到

cs 复制代码
string path = Application.persistentDatapath + "/PlayerInfo.xml";
print(Application.persistentDatapath);
2.存储xml文件

关键类 XmlDocument 用于创建节点,存储文件

关键类 XmlDeclaration 用于添加版本信息

关键类 XmlElement 节点类

存储有5步:

1.创建文本对象:

cs 复制代码
XmlDocument xml = new XmlDocument();

2.添加固定版本信息:

cs 复制代码
XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0","UTF-8","");
//创建完成过后,要添加进入文本对象中
xml.AppendChild(xmlDec);

3.添加根节点:

cs 复制代码
XmlElement root = xml.CreateElement("Root");
xml.AppendChild(root);

4.为根节点添加子节点:

cs 复制代码
XmlElement name = xml.CreateElement("name");
name.InnerText = "name";
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.InnetText = 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.保存

cs 复制代码
xml.Save(path);
3.修改xml文件

1.判断是否存在文件

cs 复制代码
if(File.Exists(path))
{
    
}

2.加载后,直接添加节点,移除节点即可

cs 复制代码
if(File.Exists(path))
{
    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("speed");
    speed.InnerText = "20";
    root2.AppendChild(speed);

    //改了记得存
    newXml.Save(path);
}

练习

XML主要用处

网络游戏:可以用于存储一些客户端的简单不重要数据

可以用于传输信息(基本不会大范围使用,因为比较耗流量)

单机游戏:用于存储游戏相关数据

用于配置游戏数据

XML序列化

什么是序列化和反序列化

序列化:把对象转化为可传输的字节序列过程称为序列化

反序列化:把字节序列还原为对象的过程称为反序列化

xml序列化流程

1.准备一个数据结构类

cs 复制代码
public class Test1
{
    public int testPublic = 10;
    private int testPrivate = 11;
    protected int testProtected = 12;
    internal int testInternal = 13;

    public string testPublicStr = "123";

    public int testPro{get;set;}

    public Test2 testClass = new Test2();
}

public class Test2
{
    public int test1 = 1;
    public float test2 = 1.1f;
    public bool test3 = true;
}

public class Test
{
    void Start()
    {
        Test1 t = new Test1();
    }
}

2.进行序列化

第一步:确定存储路径

cs 复制代码
public class Test
{
    void Start()
    {
        //其他代码
        string path = Application.persistentDataPath + "/Test.xml";
    }
}

第二步:结合 using知识点 和 StreamWriter这个流对象 来写入文件

括号内的代码:写入一个文件流,如果有该文件,直接打开并修改,如果没有该文件,直接新建一个文件

using的新用法:括号当中包裹的声明对象,会在大括号语句块结束后自动释放掉

当语句块结束,会自动帮助我们调用对象的Dispose这个方法,让其进行销毁

using一般都是配合内存占用比较大,或者有读写操作时,进行使用的

cs 复制代码
public class Test
{
    void Start()
    {
        //其他代码
        using(StreamWriter stream = new StreamWriter(path))
        {
            
        }
    }
}

第三步:进行xml文件序列化

第一个参数:文件流对象

第二个参数:想要被翻译的对象

注意:翻译机器的类型 一定要和传入的对象是一致的,不然会报错

cs 复制代码
public class Test
{
    void Start()
    {
        //其他代码
        using(StreamWriter stream = new StreamWriter(path))
        {
            XmlSerializer s = new XmlSerializer(typeof(Test));
            s.Serialize(stream,t);
        }
    }
}

关键知识点:

XmlSerializer 用于序列化对象为xml的关键类

StreamWriter 用于存储文件

using 用于方便流对象释放和销毁

这种序列化方式不支持字典!!并且只能序列化公共成员

自定义节点名或设置属性

XmlAttribute特性:变成属性格式,可以传入参数:别名

XmlElement特性:自定义节点名

XmlArrayItem特性:更改子节点名字

cs 复制代码
public class Test2
{
    [XmlAttribute(Test1)]
    public int test1 = 1;
    [XmlElement("Test222222")]
    public float test2 = 1.1f;
    [XmlAttribute()]
    public bool test3 = true;
}

XML反序列化

1.判断文件是否存在

cs 复制代码
string path = Application.persistentDataPath + "/Test.xml";
if(File.Exists(path))
{
    //其他逻辑
}

2.反序列化

关键知识:

1.using 和StreamReader

2.XmlSerializer 的 Deserialize反序列化方法

读取文件:

cs 复制代码
string path = Application.persistentDataPath + "/Test.xml";
if(File.Exists(path))
{
    //其他逻辑
    using (StreamReader reader = new StreamReader(path))
    {
        //产生了一个序列化反序列化的翻译机器
        XmlSerializer s = new XmlSerializer(typeof(Test));
        Test t = s.Deserialize(render) as Test;
    }
}

注意:List对象如果有默认值,反序列化时不会清空,会往后面添加

IXmlSerializable接口

IXmlSerializable是什么?

C#的XmlSerializer 提供了可拓展内容,可以让一些不能被序列化和反序列化的特殊类能被处理

让特殊类继承 IXmleSerializable 接口,实现其中的方法即可

自定义类实践:

cs 复制代码
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using UnityEngine;
public class Test1 : IXmlSerializable
{
    public int test1;
    public string test2;
    //返回结构,默认返回null
    public XmlSchema GetSchema()
    {
        return null;
    }

    //反序列化会执行该方法
    public void ReadXml(XmlReader reader)
    {
        //在里面自定义反序列化的规则
        //1.读属性
        //this.test1 = int.Parse( reader["test1"]);
        //this.test2 = reader.GetAttribute("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));
        reader.ReadStartElement("test1");
        this.test1 = (int)s.Deserialize(reader);
        reader.ReadEndElement();

        s = new XmlSerializer(typeof(string));
        reader.ReadStartElement("test2");
        this.test2 = (string)s.Deserialize(reader);
        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, this.test1);
        writer.WriteEndElement();

        writer.WriteStartElement("test2");
        s = new XmlSerializer(typeof(string));
        s.Serialize(writer, this.test2);
        writer.WriteEndElement();
    }
}

public class Test : MonoBehaviour
{
    private void Start()
    {
        Test1 t = new Test1();
        t.test2 = "123";
        string path = Application.persistentDataPath + "/test1.xml";

        using (StreamWriter writer = new StreamWriter(path))
        {
            //序列化"翻译机器"
            XmlSerializer s = new XmlSerializer(typeof(Test));
            s.Serialize(writer, t);
        }
    }
}

在序列化时,如果对象中的引用成员为空,那么xml里面是看不到该字段的

如何让Dictionary支持序列化反序列化

我们没有办法修改C#自带的类,所以我们可以重写一个类,用来继承Dictionary ,然后让这个类继承序列化拓展接口IXmlSerializable,最后实现里面的序列化和反序列化方法即可

cs 复制代码
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

public class SerializerDictionary<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.NodeType != XmlNodeType.EndElement)
        {
            TKey key = (TKey)keySer.Deserialize(reader);
            TValue value = (TValue)valueSer.Deserialize(reader);
            this.Add(key, value);
        }
        //跳过根节点的结束标签
        reader.Read();

    }

    public void WriteXml(XmlWriter writer)
    {
        XmlSerializer keySer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSer = new XmlSerializer(typeof(TValue));

        foreach (KeyValuePair<TKey, TValue> kv in this)
        {
            keySer.Serialize(writer, kv.Key);
            valueSer.Serialize(writer, kv.Value);
        }
    }
}

创建一个属于自己的XML文件管理类

目标:提供公共的序列化和反序列化方法给外部,方便外部存储和获取数据

数据管理类的准备:

cs 复制代码
using System;

public class XmlDataManager
{
    private static XmlDataManager instance;
    public static XmlDataManager Instance { get; } = new XmlDataManager();

    private XmlDataManager() { }

    public void SaveData(object data,string fileName)
    {

    }

    public object LoadData(Type type,string fileName)
    {
        return null;
    }
}

SaveData:

cs 复制代码
/// <summary>
/// 保存数据到xml文件
/// </summary>
/// <param name="data">数据对象</param>
/// <param name="fileName">文件名</param>
public 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);
    }
}

LoadData:

cs 复制代码
 /// <summary>
 /// 从xml文件加载数据
 /// </summary>
 /// <param name="type">对象类型</param>
 /// <param name="fileName">文件名</param>
 /// <returns></returns>
 public object LoadData(Type type, string fileName)
 {
     //1.首先判断文件是否存在
     string path = Application.persistentDataPath + "/" + fileName + ".xml";
     //2.存在就读取
     if (!File.Exists(path))
     {
         path = Application.streamingAssetsPath + "/" + fileName + ".xml";
         if(!File.Exists(path))
             //文件不存在,返回一个默认对象
             return Activator.CreateInstance(type);
     }
     using (StreamReader reader = new StreamReader(path))
     {
         //3.反序列化
         XmlSerializer s = new XmlSerializer(type);
         return s.Deserialize(reader);
     }
 }
相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习
im_AMBER4 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J4 天前
从“Hello World“ 开始 C++
c语言·c++·学习