XML基础
XML基础语法
<!--注释-->
其中version代表xml的版本,encoding代表使用的编码格式
xml文件第一行必须要写这个,属于固定格式,第一行必须是这个,注释都不行,这里是有语法错误的
xml基本语法
- 必须有一个根节点
- 标签必须成对出现
- 标签之间可以直接包裹数据,也可以包裹标签
- xml对大小写敏感
- 标签命名应当遵循变量命名规则
- 特殊符号应当使用实体引用
<PlayerInfo>
<name>Player</name>
<age>18</age>
<level>20</level>
<itemList>
<item>
<itemName>sword</itemName>
<num>1</num>
</item>
</itemList>
</PlayerInfo>
xml属性
-
属性是我们使用xml存储数据时的另一种存储方式
-
属性语法,这里我将上面的数据直接复用一下来说明
<PlayerInfo>
<name value = “Player”/>
<age value = “18”/>
<level value = “20”/>
<itemList>
<item itemName = “sword1” num = “1”/>
<item itemName = “sword2” num = “1”/>
<item itemName = “sword3” num = “1”/>
<item itemName = “sword4” num = “1”/>
</itemList>
</PlayerInfo>
xml存放位置
- 只读不写的xml文件放在Resources或者StreamingAssets文件夹下
- 动态存储的xml文件存放在Application.persistentDataPath路径下
C#读取xml文件
- XmlDocument(把数据加载到内存中, 方便读取,使用起来也比较简单)
- XmlTextReader(以流形式加载,内存占用更小,但是是单向只读,使用不是特别方便,除非有特殊需求,否则不会使用)
- Linq
使用XmlDocument读取xml
使用XmlDocument读取xml文件,我们首先需要创建一个文件对象:
XmlDocument xmlDocument = new XmlDocument();
之后我们可以使用XmlDocument的方法来加载xml文件,这里提供两种方式:
-
通过Resources加载
TextAsset textAsset = Resources.Load<TextAsset>("xmlTest"); xmlDocument.LoadXml(textAsset.text);
-
通过StreamingAssets加载
xmlDocument.Load(Application.streamingAssetsPath + @"\xmlTest.xml"); //输出xml文件对象的xml内容,包括标签 print(xmlDocument.InnerXml); //输出xml文件对象的xml内容,不包括标签 print(xmlDocument.InnerText);
读取xml文件中的节点信息流程:
- 获得父节点
- 获得子节点
- 获得节点信息
首先先来认识两个类:
- XmlNode 单个节点信息类
- XmlNodeList 多个节点信息类
//获取父节点
XmlNode root = xmlDocument.SelectSingleNode("PlayerInfo");
//获取子节点
XmlNode name = root.SelectSingleNode("name");
//获取子节点信息
print(name.InnerText);
//获取子节点
XmlNode age = root.SelectSingleNode("age");
print(age.InnerText);
//获取子节点的属性内容方式一
print(age.Attributes["value"].InnerText);
//获取子节点的属性内容方式二
print(age.Attributes.GetNamedItem("value").InnerText);
存储xml
存储是写操作,对于需要动态存储的文件我们保存在Application.persistentDataPath目录下。
对比:
- Resources:可读,不可写打包之后找不到。
- Application.streamingAssetsPath:可读,PC端可写,但移动端不可写。
- Application.dataPath:打包之后找不到,此路径常用于测试。
- Application.persistentDataPath:可读可写找得到。
存储xml文件要使用的关键类:
- XmlDocument:用于创建节点,存储文件。
- XmlDeclaration:用于添加版本信息。
- XmlElement:节点类。
存储流程:
- 创建文本对象。
- 添加固定版本信息。
- 添加根节点。
- 为根节点添加子节点。
- 保存。
private void SaveData()
{
string path = Application.streamingAssetsPath + @"\xmlTest2.xml";
/*1. 创建文本对象*/
XmlDocument xml = new XmlDocument();
/*2. 添加固定信息*/
//这句代码就相当于是<?xml version="1.0" encoding="UTF-8"?>
XmlDeclaration xmlD = xml.CreateXmlDeclaration("1.0", "UTF-8", "");
//创建完成后记得加入xml文本对象中
xml.AppendChild(xmlD);
/*3. 添加根节点*/
XmlElement root = xml.CreateElement("Root");
xml.AppendChild(root);
/*4. 为根节点添加子节点*/
XmlElement name = xml.CreateElement("Name");
name.InnerText = "tian";
root.AppendChild(name);
//可以为一个数组或者列表连续添加子节点
XmlElement intList = xml.CreateElement("intList");
for (int i = 0; i < 4; i++)
{
XmlElement ChildNode = xml.CreateElement("int");
ChildNode.InnerText = i.ToString();
intList.AppendChild(ChildNode);
}
//记得不要忘了将列表添加到根节点中
root.AppendChild(intList);
//添加属性
XmlElement ItemList = xml.CreateElement("ItemList");
for (int i = 0; i < 4; 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
- 先判断是否存在文件。
- 加载后直接添加节点或者移除节点即可。
移除节点使用RemoveChild即可。
XML优点
- XML是通用规则。
- 文件结构清晰易懂。
- 容易编辑和理解。
- 可用于网络通信交换数据。
XML缺点
- 重复工作繁多。
- 自定义数据类,需要自己实现具体的功能。且代码重复度高。
- 数据容易被修改,只要找到文件位置就可以轻易修改。
主要用途
对于网络游戏:
可以用于存储一些客户端的简单不重要数据。
可以用于传输信息(但不会大范围使用,因为比较耗流量)
对于单机游戏:
用于存储游戏相关数据。
用于配置游戏数据。
XML序列化与反序列化
序列化
什么是序列化?
简单来说把对象转换为可传输的字节序列的过程就叫做序列化,把想逃存储的内容转换为字节序列用于存储或传递。
什么是反序列化?
与序列化的过程相反,将字节序列还原成对象的过程就是反序列化,就是把存储或者收到的字节序列信息解析读取出来使用。
xml序列化准备:
- 准备一个数据结构类。
- 进行序列化。
进行序列化之前要知道的知识点:
- XmlSerializer:用于序列化对象的xml关键类。
- StreamWriter:用于存储文件。
- using 用于方便资源对象的释放和销毁(资源对象即继承了IDisposable的类)。
序列化步骤:
- 确定存储路线。
string path = Application.persistentDataPath + "/xml1.xml";
- 结合using写入文件。
public class Item
{
public int id = 1;
public int num = 20;
}
public class PlayerInfo2
{
public string name = "tian";
public int age = 10;
public int level = 10;
public Item item = new Item();
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("名字是"+name);
sb.Append("年龄是"+age.ToString());
sb.Append("等级是"+level.ToString());
return sb.ToString();
}
}
public class LoadXml : MonoBehaviour
{
private void Awake()
{
PlayerInfo2 p2 = new PlayerInfo2();
string path = Application.persistentDataPath + "/xml1.xml";
//using小括号中为创建的资源对象,当using语句块执行完后就会调用资源对象的Dispose方法来释放资源对象。
//StreamWriter需要传入一个路径,这样才知道要往那个位置去写。
using (StreamWriter sw = new StreamWriter(path))
{
//创建xml序列化对象,参数需要传入要序列化的类型。
XmlSerializer xs = new XmlSerializer(typeof(PlayerInfo2));
//序列化,第一个参数是使用哪个流对象,第二个参数是序列化那个实例。
xs.Serialize(sw, p2);
}
print(path);
}
}
xml序列化的一些缺点
- 只能存public,不支持字典序列化
自定义xml信息
- 设置节点名字[XmlElement(名字)]
- 设置属性和属性名字[XmlAtribute(名字)]
- 设置数组和数组项名字
- 设置数组的名字[XmlArray(名字)]
- 设置数组项的名字[XmlArrayItem(名字)]
反序列化
反序列化的流程:
- 判断文件是否存在使用File. Exists(path)方法。
- 如果存在就读取文件。
关键知识点:
- using语句块
- StreamReader
- XmlSerializer
读取文件的书写思路与写入文件一致:
private void DeserializeTest()
{
string path = Application.persistentDataPath + "/xml1.xml";
if (File.Exists(path))
{
using (StreamReader sr = new StreamReader(path))
{
XmlSerializer xs = new XmlSerializer(typeof(PlayerInfo2));
//将得到的信息转换成对应的数据结构
PlayerInfo2 p2 = xs.Deserialize(sr) as PlayerInfo2;
}
}
}
注意:List对象如果有默认值反序列化时不会清空重新加,而是会往后继续添加。
IXmlSerializable接口
IXmlSerializable是干什么的?
C#为XmlSerializer提供了可拓展内容。可以让一些不能被序列化和反序列化的特殊类也可以被处理,比如字典,只需让特殊类实现此接口即可。
此接口需要实现的方法:
//返回结构,返回null即可
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
///反序列化时会调用的方法
/// </summary>
/// <param name="reader"></param>
public void ReadXml(XmlReader reader)
{
//在里面可以自定义反序列化的规则,但要使用XmlReader这个参数类
}
/// <summary>
/// 序列化时会自动调用的方法
/// </summary>
/// <param name="writer"></param>
public void WriteXml(XmlWriter writer)
{
//在里面可以自定义序列化的规则,但是要使用XmlWriter这个参数类
}
操作属性
写属性:
/// <summary>
/// 当序列化此类时就会按照这里的规则来序列化
/// </summary>
/// <param name="writer"></param>
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("age", this.age.ToString());
}
读属性:
/// <summary>
/// 当反序列化此类时就会按照这里的规则来反序列化
/// </summary>
/// <param name="reader"></param>
public void ReadXml(XmlReader reader)
{
this.age = int.Parse(reader["age"]);
}
操作节点
写节点:
/// <summary>
/// 当序列化此类时就会按照这里的规则来序列化
/// </summary>
/// <param name="writer"></param>
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("age", this.age.ToString());
}
读节点:
/// <summary>
/// 当反序列化此类时就会按照这里的规则来反序列化
/// </summary>
/// <param name="reader"></param>
public void ReadXml(XmlReader reader)
{
//由于节点读取比较特殊,使用reader.Read()每次只会向后读取一个标签,所以我们需要使用循环来读取我们想要的信息
while (reader.NodeType == XmlNodeType.Element)
{
//每当读取到信息后我们需要用switch来判断不同的信息有不同的赋值方式。
switch (reader.Name)
{
case "age":
reader.Read();
this.age = int.Parse(reader.Value);
break;
}
}
}
包裹节点
写包裹节点:
/// <summary>
/// 当序列化此类时就会按照这里的规则来序列化
/// </summary>
/// <param name="writer"></param>
public void WriteXml(XmlWriter writer)
{
XmlSerializer xs = new XmlSerializer(typeof(int));
//start和End一定要成对出现
writer.WriteStartElement("age");
xs.Serialize(writer, age);
writer.WriteEndElement();
}
读包裹节点:
/// <summary>
/// 当反序列化此类时就会按照这里的规则来反序列化
/// </summary>
/// <param name="reader"></param>
public void ReadXml(XmlReader reader)
{
XmlSerializer xs = new XmlSerializer(typeof(int));
//跳过根节点
reader.Read();
//start和End一定要成对出现
reader.ReadStartElement("age");
age = (int)xs.Deserialize(reader);
reader.ReadEndElement();
}
让Dictionary支持序列化和反序列化
想让Dictionary支持xml序列化和反序列化的会有一个问题:
我们没有办法修改C#自带的类。
解决方案:重写一个Dictionary类并让这个类继承Dictionary和实现IXmlSerializable接口。
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace CsharpBaseModule.SaveData.Xml
{
public class SerialiazedDictionary<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);
}
}
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);
}
}
}
}