基本语法
注释
<!--注释-->
<!--
多行
注释
-->
固定内容
<!--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文档必须有根元素
特殊的符号应该用实体引用
< 对应< 小于
> 对应> 大于
& 对应& 和号
&apos对应' 单引号
" 对应“ 双引号
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是国际通用规则
跨平台(游戏、软件、网页等等都能用)
文件结构清晰易懂
非常容易编辑和理解
可以用于网络通信交换数据
缺点:
重复工作繁多
自定义数据类都需要自己去实现存储读取功能且代码相似度极高
数据容易被修改
只要找到文件位置就可以轻易修改数据
主要用处:
可以用于存储一些客户端的简单不重要的数据
可以用于传输信息(基本上不会大范围使用,因为比较耗流量)
单机游戏:
用于存储游戏相关数据
用于配置游戏数据