Unity:XML笔记(二)——Xml序列化、反序列化、IXmlSerializable接口

写在前面:

写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。

三、Xml序列化

序列化就是把想要存储的内容转换为字节序列用于存储或传递。

1、序列化

我们先创建一个类,之后利用Xml序列化来存储这个类:

public class Test
{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 int[] arrayInt = new int[3] { 5, 6, 7 };public List<int> listInt = new List<int>() { 1, 2, 3, 4 };public List<Test2> listItem = new List<Test2>() { new Test2(), new Test2() };
}public class Test2
{public int test1 = 1;public float test2 = 1.1f;public bool test3 = true;
}

Xml序列化的第一步是确认存储路径:string path = Application.persistentDataPath + "/Test.xml";该存储路径设置方式和之前使用XmlDocument存储的方式一样。

序列化存储需要在一个using代码块中,如下:

using (StreamWriter stream = new StreamWriter(path)){          }

现在对这行代码进行解释。StreamWriter是向文件流写入字符的类,属于System.IO命名空间。括号内的代码的意思是:写入一个文件流,如果有该文件,直接打开并修改;如果没有该文件,自动释放掉。new StreamWriter()
这里还涉及到using 的新用法:括号内包裹的声明的对象,会在大括号语句块结束后自动释放掉。当语句块结束时会自动调用对象的Dispose方法,让其销毁。using一般是配合 内存占用比较大或者有读写操作时进行使用。

在语句块中,我们需要创建一个“序列化机器”来将我们的类序列化:

 XmlSerializer s = new XmlSerializer(typeof(Test));

需要注意的是,序列化机器的类型,一定是要和我们需要序列化存储的对象是同样的类型。接下来就可以使用序列化机器进行序列化:

s.Serialize(stream, lt);

这句代码通过序列化机器,对我们类对象进行翻译,将其翻译成xml文件写入到对应文件中。第一个参数:文件流对象;第二个参数:想要被翻译的对象。这样,就完成了序列化存储:

public class lession1 : MonoBehaviour
{void Start(){Test lt = new Test();string path = Application.persistentDataPath + "/Test.xml";using (StreamWriter stream = new StreamWriter(path)){XmlSerializer s = new XmlSerializer(typeof(Test));s.Serialize(stream, lt);}}
}

我们可以在我们设置的保存路径中,找到序列化后的Xml文件:

<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><testPublic>10</testPublic><testPublicStr>123</testPublicStr><testClass><test1>1</test1><test2>1.1</test2><test3>true</test3></testClass><arrayInt><int>5</int><int>6</int><int>7</int></arrayInt><listInt><int>1</int><int>2</int><int>3</int><int>4</int></listInt><listItem><Test2><test1>1</test1><test2>1.1</test2><test3>true</test3></Test2><Test2><test1>1</test1><test2>1.1</test2><test3>true</test3></Test2></listItem><testPro>0</testPro>
</Test>

可以看到,我们类中private、protected、internal修饰的变量都没有被序列化。也就是说,Xml序列化方法只能序列化公共成员。此外,不支持字典序列化,如果类中有字典,就会报错。

2、修改节点信息或设置属性信息

可以通过特性修改节点信息或者设置属性信息。例如,如果需要将Test2类中的成员以属性方式存储,可以加上特性:[XmlAttribute()],如果想修改属性的名字,可以在括号内传入想要的属性名:[XmlAttribute("Test1")。

public class Test2
{[XmlAttribute("Test1")]public int test1 = 1;[XmlAttribute()]public float test2 = 1.1f;[XmlAttribute()]public bool test3 = true;
}

如果想要修改变量的名字,可以加上特性:[XmlElement("testPublic111")],括号内传入属性名:

[XmlElement("testPublic111")]
public int testPublic = 10;

如果想要修改数组的名字,可以添加特性:[XmlArray("IntList")],如果想要修改数组中元素节点的名字,可以用特性:[XmlArrayItem("Int32")]

[XmlArray("IntList")]
[XmlArrayItem("Int32")]
public int[] arrayInt = new int[3] { 5, 6, 7 };

四、Xml反序列化

反序列化就是把存储或收到的字节序列信息解析读取出来使用。

1、判断文件是否存在

在反序列化之前,需要判断文件是否存在。判断方式是:File.Exists(path),括号中传入的是文件地址。

void Start()
{string path = Application.persistentDataPath + "/Test.xml";if(File.Exists(path)){}
}

2、反序列化

反序列化和序列化基本相同,区别就是这里使用的是StreamReader,从流中读出字符的类。如下:using (StreamReader reader = new StreamReader(path)){  }

同样的,在using语句块中,初始化一个反序列化翻译机器: XmlSerializer s = new XmlSerializer(typeof(Test));

然后调用s.Deserialize(reader)方法即可完成反序列化:

void Start()
{string path = Application.persistentDataPath + "/Test.xml";if(File.Exists(path)){using (StreamReader reader = new StreamReader(path)){XmlSerializer s = new XmlSerializer(typeof(Test));Test lt = s.Deserialize(reader) as Test;}}
}

这里需要注意的是,在三中定义Test类时,为了方便,很多值都是直接在类中初始化的。对于List对象,如果有默认值,反序列化时不会清空而是会往后继续添加,所以最好不要在类中直接初始化。

五、IXmlSerializable接口

C#的的XmlSerializer提供了可扩展内容,可以让一些不能被序列化和反序列化的特殊类能被处理,例如字典。让特殊类继承 IXmlSerializable接口实现其中的方法即可。

1、自定义序列化和反序列化

先按三、四所学知识创建一个类并书写序列化和反序列化方法:

public class Test3
{public int test1;public string test2;
}public class lession3 : MonoBehaviour
{void Start(){Test3 t = new Test3();string path = Application.persistentDataPath + "/Test3.xml";using (StreamWriter writer = new StreamWriter(path)){XmlSerializer s = new XmlSerializer(typeof(Test3));s.Serialize(writer, t);}using(StreamReader reader = new StreamReader(path)){XmlSerializer s = new XmlSerializer(typeof(Test3));Test3 t2 = s.Deserialize(reader) as Test3;}}
}

这段代码实现了对类Test3进行序列化和反序列化。这里补充一点,在序列化时 如果对象中的引用成员为空 那么xml里面是看不到该字段的,所以这里的xml文件中没有string。

接下来,我们就可以开始自定义序列化方法和反序列化方法。首先需要类Test3继承IXmlSerializable接口,并在Test3中重写接口中的函数:

其中public XmlSchema GetSchema()暂时不需要了解,该函数返回结构,直接return null即可。public void ReadXml(XmlReader reader)是反序列化会调用的方法,在其中书写的代码能够替换掉该类原来的反序列化函数。public void WriteXml(XmlWriter writer)是序列化会调用的方法。这两个函数可以定义序列化的规则。

public class Test3:IXmlSerializable
{public int test1;public string test2;//返回结构public XmlSchema GetSchema(){return null;}//反序列化时会自动调用方法public void ReadXml(XmlReader reader){}//序列化时会自动调用的方法public void WriteXml(XmlWriter writer){}
}

(1)自定义读属性和写属性

首先来自定义读属性和写属性的规则。如果要自定义序列化的规则,一定会用到XmlWriter、XmlReader中的一些方法。

对于写属性,XmlWriter是写入器对象提供一系列方法来生成和写入 XML 格式的数据。可以使用:writer.WriteAttributeString()写入属性,括号内传入的第一个参数是属性名,第二个参数是属性的内容。

对于读属性,XmlReader则是用于读入数据的工具类。可以通过reader["test1"]来获得[]内属性的值。如下所示:

public void ReadXml(XmlReader reader)
{this.test1 = int.Parse(reader["test1"]);this.test2 = reader["test2"];    
}public void WriteXml(XmlWriter writer)
{writer.WriteAttributeString("test1", this.test1.ToString());writer.WriteAttributeString("test2", this.test2)
}

(2)自定义读节点和写节点

①方式1

写节点可以通过XmlWriter的方法:writer.WriteElementString(),括号内分别传入节点名、节点的数值即可。

读节点需要用reader.Read(),表示逐步读。一开始Reader位于根节点Test3,调用reader.Read()后读到test1节点,继续调用reader.Read()后读到test1节点包裹的内容,此时就可以将该值读出来。继续调用reader.Read()后读到尾部包裹节点,再调用reader.Read()读到test2节点...以此类推。

  这里为了方便看所以给test2赋值为了123再进行读写数据。

public void ReadXml(XmlReader reader)
{reader.Read();//这时是读到的test1节点reader.Read();//这时是读到的test1节点包裹的内容this.test1 = int.Parse(reader.Value);reader.Read();//尾部包裹节点reader.Read();//这时读到的是test2节点reader.Read();//读到的是test2节点包裹的内容this.test2 = reader.Value;
}public void WriteXml(XmlWriter writer)
{writer.WriteElementString("test1", this.test1.ToString());writer.WriteElementString("test2", this.test2);
}
②方式2

方式①读节点的重复代码太多了,可以采用方式2这种写法:

while(reader.Read())
{if(reader.NodeType == XmlNodeType.Element){switch(reader.Name){case "test1":reader.Read();this. test1 = int.Parse(reader.Value);break;case "tese2":reader.Read();this.test2 = reader.Value;break;}}
}

(3)自定义读写包裹节点

如果想自定义写包裹节点,类似于下图,test1中包裹着节点int,test2中包裹着节点string

①写

以第一个写int为例。需要先声明一个序列化器

XmlSerializer s = new XmlSerializer(typeof(int));

然后使用节点相关API:这两句代码表示开始节点test1,结束节点test1,在这两行中间定义的节点就会被包裹在节点test1中间。

writer.WriteStartElement("test1");writer.WriteEndElement();

例如,在以上两句代码中间使用序列化器写入节点teat的值,序列化器是int类型的,所以会生成<int>0</int>节点:

writer.WriteStartElement("test1");
s.Serialize(writer, test1);
writer.WriteEndElement();
②读

同样以读int为例,首先需要创建一个序列化器:

XmlSerializer s = new XmlSerializer(typeof(int));

调用reader.Read()让reader指向test1:

reader.Read();

然后可以使用以下两句代码,表示读test1节点的开始于结束:

reader.ReadStartElement("test1");reader.ReadEndElement();

最后在中间写需要读入的数据即可:

reader.ReadStartElement("test1");
test1 = (int)s.Deserialize(reader);
reader.ReadEndElement();

完整代码:

public void ReadXml(XmlReader reader)
{XmlSerializer s = new XmlSerializer(typeof(int));reader.Read();reader.ReadStartElement("test1");test1 = (int)s.Deserialize(reader);reader.ReadEndElement();XmlSerializer s2 = new XmlSerializer(typeof(string));reader.ReadStartElement("test2");test2 = (string)s2.Deserialize(reader);reader.ReadEndElement();
}public void WriteXml(XmlWriter writer)
{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();
}

2、让Dictionary支持序列化反序列化

相当于以上知识的一个小应用,可以拓展一个可以被序列化和反序列化的字典类,所以不多解释:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95564.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java注解、Lambda表达式、Servlet

一、Java注解注解的概念&#xff1a; Java注解是代码中的元数据&#xff0c;可以用于描述其他代码。注解在编译、类加载、运行时被处理&#xff0c;并且不会改变代码逻辑。注解的用途&#xff1a; 提供代码元信息&#xff0c;如 Override 表明一个方法覆盖了父类的方法。 编译检…

【单片机day02】

GPIO&#xff1a;Genral Purpose Input/Output&#xff0c;GPIO是51单片机和外界交互最基本的方式工作模式&#xff1a;输出模式&#xff1a;单片机给定引脚一个电平(高电平(5V) 低电平(0V)),控制引脚实现高低电平输入模式&#xff1a;检测引脚电平变化GPIO水龙头输出模式&…

Java中最常用的设计模式

Java设计模式之结构型—代理模式-CSDN博客 观察者模式详解-CSDN博客 单例模式详解-CSDN博客 Java设计模式之结构型—享元模式-CSDN博客 Java设计模式之创建型—建造者模式-CSDN博客 Java设计模式之结构型—工厂模式-CSDN博客 Java设计模式之结构型—适配器模式-CSDN博客 …

使用Axure动态面板制作轮播图案例详解

在现代网页设计中&#xff0c;轮播图&#xff08;Carousel&#xff09;是一种常见且高效的展示方式&#xff0c;用于在同一空间内循环展示多张图片或内容。Axure RP作为一款强大的原型设计工具&#xff0c;提供了动态面板和丰富的交互事件功能&#xff0c;使得制作轮播图变得简…

VUE的中 computed: { ...mapState([‘auditObj‘]), }写法详解

具体解析&#xff1a;computed&#xff1a;这是 Vue 组件选项中的计算属性&#xff0c;用于声明依赖于其他数据而存在的派生数据。计算属性会根据依赖进行缓存&#xff0c;只有当依赖的数据发生变化时才会重新计算。mapState&#xff1a;这是 Vuex 提供的一个辅助函数&#xff…

【ProtoBuf】以 “数据秘语” 筑联络:通讯录项目实战 1.0 启步札记

文章目录引言筑路之备&#xff1a;快速上手ProtoBuf步骤一&#xff1a;创建.proto文件⽂件规范添加注释指定 proto3 语法package 声明符定义消息&#xff08;message&#xff09;定义消息字段【定义联系人 message】字段唯一编号的范围步骤2&#xff1a;编译 contacts.proto ⽂…

在 macOS 下升级 Python 几种常见的方法

在 macOS 下升级 Python 有几种常见的方法&#xff0c;具体取决于你最初是如何安装 Python 的。了解你的安装方式是关键。 首先&#xff0c;你需要知道你当前 Python 版本以及它的安装路径。 检查 Python 版本&#xff1a; python --version # 可能指向 Python 2.x python3 …

Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十五天

三、Shell脚本编程 Shell脚本语言的运算 算数运算 shell支持算术运算&#xff0c;但只支持整数&#xff0c;不支持小数 Bash中的算术运算 -- 加法运算 -- - 减法运算 -- * 乘法运算 -- / 除法运算 -- % 取模&#xff0c;即取余数 -- ** 乘方 ​ #乘法符号在有些场景需要转…

SpringAI系列---【多租户记忆和淘汰策略】

1.多租户工作原理 2.引入jdbc的pom spring官网链接&#xff1a;https://docs.spring.io/spring-ai/reference/api/chat-memory.html&#xff0c;推荐使用官网的jdbc。 阿里巴巴ai链接&#xff1a;https://github.com/alibaba/spring-ai-alibaba/tree/main/community/memories j…

Linux gzip 命令详解:从基础到高级用法

Linux gzip 命令详解&#xff1a;从基础到高级用法 在 Linux 系统中&#xff0c;文件压缩与解压缩是日常运维和文件管理的常见操作。gzip&#xff08;GNU Zip&#xff09;作为一款经典的压缩工具&#xff0c;凭借其高效的压缩算法和简洁的使用方式&#xff0c;成为 Linux 用户处…

Redis有什么优点和缺点?

优点&#xff1a;极致性能&#xff1a; 基于内存操作和高效的单线程 I/O 模型&#xff0c;读写速度极快。数据结构丰富&#xff1a; 支持多种数据结构&#xff0c;如 String、Hash、List、Set、ZSet、Stream、Geo 等&#xff0c;编程模型灵活。持久化与高可用&#xff1a; 提供…

NestJS 3 分钟搭好 MySQL + MongoDB,CRUD 复制粘贴直接运行

基于上一篇内容《为什么现代 Node 后端都选 NestJS TypeScript&#xff1f;这组合真香了》&#xff0c;这篇文章继续写数据库的连接。 所以今天把MySQL、MongoDB全接上&#xff0c;做个小实例。朋友们项目里用什么数据库可以视情况而定。 这里的功能分别为&#xff1a; MySQ…

用了企业微信 AI 半年,这 5 个功能让我彻底告别重复劳动

每天上班不是在整理会议纪要&#xff0c;就是在翻聊天记录找文件&#xff0c;写文档还要自己抠数据…… 这些重复劳动是不是也在消耗你的时间&#xff1f;作为用了企业微信 AI 功能半年的 “老用户”&#xff0c;我必须说&#xff1a;企业微信 AI 的这 5 个功能&#xff0c;真的…

从入门到高手,Linux就应该这样学【好书推荐】

从入门到高手&#xff0c;请这样学Linux 一、Linux基础与终端操作 1.1 Linux简介 Linux 是一种开源的类 Unix 操作系统&#xff0c;以其稳定性、安全性和高效性被广泛应用于服务器、嵌入式系统及开发环境中。掌握基本命令和操作技巧是 Linux 学习的关键。 1.2 终端基础 打开…

【数据可视化-104】安徽省2025年上半年GDP数据可视化分析:用Python和Pyecharts打造炫酷大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

组件库UI自动化

一、背景 背景&#xff1a; 组件库全局改动场景多&#xff0c;组件之间耦合场景多–时常需要全场景回归组件库demo有200多个&#xff0c;手动全局回归耗时耗力细微偏差纯视觉无法辨别 可行性分析&#xff1a; 组件库功能占比 L1&#xff08;视觉层&#xff09;&#xff1a;图片…

面试题:JVM与G1要点总结

一.Java内存区域 1.运行时数据区的介绍 2.站在线程的角度看Java内存区域 3.深入分析堆和栈的区别 4.方法的出入栈和栈上分配、逃逸分析及TLAB 5.虚拟机中的对象创建步骤 6.对象的内存布局 1.运行时数据区的介绍 运行时数据区的类型&#xff1a;程序计数器、Java虚拟机栈、本地方…

车辆安全供电系统开发原则和实践

摘要在汽车行业中&#xff0c;安全应用的重要性在不断提升&#xff0c;例如受车辆自动化发展以及机械备用系统重要性降低的影响。为应对这些趋势&#xff0c;安全相关的电气和 / 或电子系统&#xff08;E/E 系统&#xff09;的电源输入必须由供电系统来保障&#xff0c;这使得功…

WebSocket客户端库:websocket-fruge365

&#x1f680; 从零开始打造一个WebSocket客户端库&#xff1a;websocket-fruge365 &#x1f4d6; 前言 在现代Web开发中&#xff0c;实时通信已经成为不可或缺的功能。无论是聊天应用、实时数据监控&#xff0c;还是在线协作工具&#xff0c;WebSocket都扮演着重要角色。然而…

rocketmq批量执行跑批任务报错

rocketmq批量执行跑批任务&#xff0c;报下面的错误&#xff0c;怎么处理一下呢&#xff1f;是修改配置还是修改代码还是&#xff1f; org.apache.rocketmq.client.exception.MQBrokerException: CODE: 215 DESC: [FLOW]client has exhausted the send quota for the current …