XML数据持久化

基本语法

注释

<!--注释-->
<!--
多行
注释
-->

固定内容

<!--version代表版本 encoding代表编码格式 -->
<!-- 编码格式:在读取文件时解析字符串时用的编码
不同的编码格式 不同字符在内存中的二进制不一样 每个字符对应一个数字 例如ASCII码 --> 
<?xml version="1.0" encoding="UTF-8"?>

基本语法

<!-- 必须有一个根节点 -->
<!-- 以标签的形式存储数据 有点类似于html-->
<!--<节点名>可以填写数据 或者 在其中包裹别的节点</节点名>-->
<PlayerInfo>
	<name>name</name>
	<atk> 10 </atk>
	<ItemList>
		<Item>
			<id>1</id>
			<num>1</num>
		</Item>
		<Item>
			<id>2</id>
			<num>2</num>
		</Item>
		<Item>
			<id>3</id>
			<num>3</num>
		</Item>
	</ItemList>
</PlayerInfo>

基本规则

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

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

XML标签对大小写敏感

XML文档必须有根元素

特殊的符号应该用实体引用
&lt  对应<        小于
&gt 对应>        大于
&amp 对应&        和号
&apos对应'        单引号
&quot 对应“        双引号

XML属性

属性语法

<PlayerInfo>
	<name>name</name>
	<atk> 10 </atk>
	<ItemList>
		<!-- 属性 通过空格隔开 属性名 = 引号包裹的内容 
		以下是一种简写 省去关闭标签 (如果不需要存储数据的话)
		-->
		<Item id = "1" num = "1">Item</Item>
		<Item id = "2" num = "2"/>
		<Item id = "3" num = "3"/>
	</ItemList>
</PlayerInfo>

属性和元素节点的区别

没有什么区别,写法不同,表达的意思相似,可以选择自己喜欢的方式来记录数据

如何查语法错误

元素标签必须配对

属性必须有引号

注意命名

菜鸟教程上可以验证

C#读取XML文件

C#读取XML的方法有以下几种 :

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

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

Linq(Linq单独讲)

读取XML文件信息

        XmlDocument xml = new XmlDocument();
        //通过XmlDocument读取xml文件有两个API
        //直接根据xml字符串内容来加载xml文件
        //存放在Resources文件夹下的xml文件加载处理
        TextAsset asset = Resources.Load<TextAsset>("TestXml");
        print(asset.text);
        //通过这个方法就能够翻译字符串为xml对象
        xml.LoadXml(asset.text);
        //通过xml文件的路径进行加载
        xml.Load(Application.streamingAssetsPath + "/TestXml.xml");

读取元素和属性信息

        //获取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 获取一个节点 下的所有同名节点

        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);
        }

C#存储XML文件

决定存储在哪个文件夹下

xml文件在Unity中一定是使用各个平台都可读可写 可找到的路径

Resources可读但 不可写,打包后也找不到

Application.streamingAssetsPath可读 在PC端可写,但IOS和安卓不可写,一般用于读取默认配置文件进行初始化

Application.dataPath 打包后找不到

Application.persistentDataPath可读可写找得到,所以用这个

        string path = Application.persistentDataPath + "/PlayerIndo2.xml";
        print(Application.persistentDataPath);

存储XML文件

        //关键类 XmlDocument 用于创建节点 存储文件
        //关键类 XmlDeclaration 用于添加版本信息
        //关键类 XmlElement 节点类

        //存储有5步
        //创建文本对象

        XmlDocument xml = new XmlDocument();
        //添加固定版本信息
        //相当于创建<?xml version="1.0" encoding="UTF-8"?>这句话
        XmlDeclaration xmlDec = xml.CreateXmlDeclaration("1.0", "UTF-8", "");
        //创建完后要添加进文本对象中
        xml.AppendChild(xmlDec);
        //添加根节点
        XmlElement root = xml.CreateElement("Root");
        xml.AppendChild(root);
        //为根结点添加子节点
        //加了 name子节点
        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.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", (10 * i).ToString());
            itemList.AppendChild(childNode);
        }
        root.AppendChild(itemList);
        //保存
        xml.Save(path);

修改XML文件

        //先判断是否存在文件
        if (File.Exists(path))
        {           
            //加载后直接添加节点 移除节点即可
            XmlDocument newXml = new XmlDocument();
            newXml.Load(path);
            //修改就是在原文件基础上移除或者 添加
            XmlNode node = newXml.SelectSingleNode("Root").SelectSingleNode("atk");
            print(node.InnerText);
            //简便写法通过/ 来区分父子关系
            node = newXml.SelectSingleNode("Root/atk");
            print(node.InnerText);

            //得到父节点
            XmlNode root2 = newXml.SelectSingleNode("Root");
            //移除子节点
            root2.RemoveChild(node);
            //添加节点
            XmlElement speed = newXml.CreateElement("moveSpeed");
            speed.InnerText = "20";
            root2.AppendChild(speed);
            //改完保存
            newXml.Save(path);
        }

XML序列化

public class Lesson1Test
{
    //只改名字
    [XmlElement("TestPubilc123123")]
    public int testPublic = 10;
    public int testPrivate = 20;
    protected int testProtected = 30;
    internal int testInternal = 40;
    public string testPublicStr = "123";
    public int tesatPro {  get; set; }
    public Lesson1Test2 testClass = new Lesson1Test2();
    public int[] arrayInt = new int[3] { 5, 6, 7 };
    //改List的名字
    [XmlArray("IntList")]
    //改List里面成员的名字
    [XmlArrayItem("Int32")]
    public List<int> listInt = new List<int>() { 1, 2, 3, 4 };

    public List<Lesson1Test2> listItem = new List<Lesson1Test2>() { new Lesson1Test2(), new Lesson1Test2() };

}
public class Lesson1Test2
{
    //以属性的形式存储 括号内为改的名字
    [XmlAttribute("Test1")]
    public int test1 = 1;
    [XmlAttribute]
    public float test2 = 1.1f;
    [XmlAttribute]
    public bool test3 = true;
}
public class Lesson1 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Lesson1Test lt = new Lesson1Test();
        //确定存储路径
        string path = Application.persistentDataPath + "/Lesson1Test.xml";
        //结合using知识点和SreamWriter这个流对象来写入文件
        //括号内的代码:写入一个文件流 如果有该文件直接打开并修改,如果没有就创建一个
        //using新用法:括号内包裹的申明的对象 会在大括号语句块结束后自动释放
        //当语句块结束 会自动调用对象的Dispose这个函数 让其销毁
        //using一般配合 内存占用比较大 或者有读写操作时 进行使用的
        using (StreamWriter stream = new StreamWriter(path))
        {
            //进行xml文件序列化
            XmlSerializer s = new XmlSerializer(typeof(Lesson1Test));//翻译机器
            s.Serialize(stream, lt);//通过序列化对象 将类对象翻译成xml文件,写到对应的文件中
            //第一个参数:文件流对象
            //第二个参数:想要被翻译的对象
            //注意:翻译机器的类型要和传入的对象类型一致,否则报错
            //私有、保护、内联的无法存储,只能存储公共成员
            //能存储数组和List但不存储字典
        }
    }

}

XML反序列化

    void Start()
    {
        //判断文件是否存在
        string path = Application.persistentDataPath + "/Lesson1Test.xml";
        if (File.Exists(path))
        {
            using(StreamReader reader = new StreamReader(path))
            {
                //产生一个反序列化的翻译机器
                XmlSerializer  s = new XmlSerializer(typeof(Lesson1Test));
                //注意:类中的List最好不要在类里面就初始化,反序列化读取不会覆盖初始化的信息,导致读出数据不准确
                Lesson1Test lt = s.Deserialize(reader) as Lesson1Test;
            }
        }
    }

IXmlSerializable接口

C#的Xmlserializer 提供了可拓展内容
可以让一些不能被序列化和反序列化的特殊类能被处理
让特殊类继承 IXmlserializable 接口 实现其中的方法即可

public class TestLesson3 : IXmlSerializable
{
    public int test1;
    public string test2;//没初始化,默认值为空不会存入文件
    //返回结构
    public XmlSchema GetSchema()
    {
        return null;
    }
    //反序列化时 自动调用的函数
    public void ReadXml(XmlReader reader)
    {
        //读属性
        this.test1 = int.Parse(reader["test1"]);
        this.test2 = reader["test2"];

        //读节点

        //方式一
        reader.Read();//读到test1节点
        reader.Read();//读到test1节点包裹的内容
        this.test1 = int.Parse(reader.Value);
        reader.Read();//读到test1尾部包裹节点
        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;
                }
            }
        }

        //读子节点
        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("test1");
        test1 = (int)s.Deserialize(reader);
        reader.ReadEndElement();
    }

    //序列化时 自动调用的函数
    public void WriteXml(XmlWriter writer)
    {
        //自定义序列化的规则,要用到XmlWriter中的一些函数来进行序列化
        //写属性
        writer.WriteAttributeString("test1",this.test1.ToString());
        writer.WriteAttributeString("test2", this.test2);

        //写节点
        writer.WriteElementString("test1", this.test1.ToString());
        writer.WriteElementString("test2", this.test2);

        //写子节点
        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 update
    void Start()
    {
        TestLesson3 t = new TestLesson3 ();
        t.test2 = "123";
        string path = Application.persistentDataPath + "/TestLesson3.xml";
        print(Application.persistentDataPath);
        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;
        }
    }
}

Dictionary支持序列化反序列化

自定义一个类继承Dictionary然后继承IXmlSerializable接口

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);
        }
    }
}

 应用

public class TestLesson4
{
    public int test1;
    public SerializerDictionary<int, string> dic;
}
public class Lesson4 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        string path = Application.persistentDataPath + "/TestLesson4.xml";
        print(Application.persistentDataPath);
        TestLesson4 tl4 = new TestLesson4();
/*        tl4.dic = new SerializerDictionary<int, string>();
        tl4.dic.Add(1, "123");
        tl4.dic.Add(2,"234");
        tl4.dic.Add(3, "345");
        using (StreamWriter writer = new StreamWriter(path))
        {
            XmlSerializer s = new XmlSerializer(typeof(TestLesson4));
            s.Serialize(writer, tl4);
        }*/
        using (StreamReader reader = new StreamReader(path))
        {
            XmlSerializer s =new XmlSerializer(typeof(TestLesson4));
            tl4 = s.Deserialize(reader) as TestLesson4;
        }
    }
}

总结

优点:

XML是国际通用规则
跨平台(游戏、软件、网页等等都能用)
文件结构清晰易懂
非常容易编辑和理解
可以用于网络通信交换数据

缺点:

重复工作繁多
自定义数据类都需要自己去实现存储读取功能且代码相似度极高
数据容易被修改
只要找到文件位置就可以轻易修改数据

主要用处:

可以用于存储一些客户端的简单不重要的数据
可以用于传输信息(基本上不会大范围使用,因为比较耗流量)
 

单机游戏:

用于存储游戏相关数据
用于配置游戏数据

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值