1. 项目概述
在Unity游戏开发或者任何C#应用开发中,数据持久化是一个绕不开的话题。从简单的游戏存档、配置表,到复杂的关卡数据、角色属性,我们都需要一种可靠的方式来存储和读取。XML,作为一种结构清晰、可读性强且跨平台的数据格式,一直是C#生态中的主力军之一。但很多开发者,尤其是刚接触Unity的朋友,面对
XmlDocument
、
XmlReader/Writer
、
LINQ to XML
这一堆名词时,常常感到困惑:它们有什么区别?在Unity里到底该用哪个?哪个性能好?哪个写起来爽?
我自己在项目里踩过不少坑,从早期笨拙地用
XmlDocument
遍历节点,到后来拥抱
LINQ to XML
的简洁优雅,再到处理上百MB的配置文件时被逼着去研究
XmlReader
的流式读取。今天,我就结合这些年的实战经验,把这几种常用的XML读写方式掰开揉碎了讲清楚。我们不止看“怎么用”,更要弄明白“为什么用”以及“什么时候用”,让你在面对不同场景时,能做出最合适的选择。
2. XML处理方案全景与核心决策逻辑
在Unity/C#中处理XML,主流方案可以概括为三大流派,它们各有各的脾气和适用场景,选错了不仅代码写得别扭,还可能给项目埋下性能隐患。
2.1 三大核心方案对比
我们可以用一个表格来快速把握它们的核心特征:
| 特性 | XmlDocument (DOM解析) | LINQ to XML | XmlReader / XmlWriter (流式解析) |
|---|---|---|---|
| 编程模型 | 基于W3C DOM标准,树形结构 | 基于LINQ的函数式/声明式 | 基于流的只进(forward-only)模型 |
| 内存占用 | 高 。需将整个XML文档加载到内存中形成节点树。 | 中高 。同样加载整个文档,但对象模型更轻量。 | 极低 。一次只在内存中处理一个节点或一小块数据。 |
| 读写能力 | 支持随机读取和修改,可保存回文件。 | 支持灵活的查询和修改,可保存回文件。 |
XmlReader
只读;
XmlWriter
只写。修改需配合其他模型。
|
| 易用性 | 中等。API偏底层,操作节点和属性略显繁琐。 | 高 。LINQ语法直观,代码简洁,接近操作普通集合。 | 低 。需要手动控制读取流程,处理状态,代码复杂度高。 |
| 性能特点 | 加载慢(需构建完整树),查询和修改方便。 |
加载速度优于
XmlDocument
,查询(特别是复杂查询)极快。
| 加载和读取速度最快 ,适合大数据量或内存敏感场景。 |
| 典型场景 | 中小型配置文件,需要频繁随机访问和修改的场景。 | 绝大多数Unity游戏数据配置 (如物品表、对话树、本地化),代码可读性要求高的场景。 | 读取超大型XML文件(如地图数据、批量导出数据),嵌入式设备等内存严格受限的环境。 |
2.2 Unity环境下的特殊考量
在Unity中选择XML处理方案,不能只考虑C#标准库,还得把Unity引擎的特性考虑进去。
-
平台兼容性与AOT编译
:Unity的脚本后端有Mono和IL2CPP。IL2CPP会将C#代码转换为C++,并进行AOT(Ahead-of-Time)编译。对于大量使用反射的
XmlSerializer,在IL2CPP下可能会遇到问题,需要预生成序列化程序集。而XmlDocument、LINQ to XML和XmlReader/Writer基于代码显式调用,通常没有问题。 -
资源加载方式
:Unity中XML文件可能作为
TextAsset放在Resources文件夹中,也可能放在StreamingAssets或可写目录(如Application.persistentDataPath)中。LINQ to XML的XDocument.Parse(TextAsset.text)是从TextAsset加载最便捷的方式。而从可写目录加载文件,则需要注意平台路径差异(Application.streamingAssetsPath在Android上是只读的,需用UnityWebRequest读取)。 -
序列化与工作流
:虽然本文重点在手动解析,但要知道Unity内置的
JsonUtility和第三方库如Newtonsoft.Json(需导入)对JSON支持更好。如果数据格式完全可控,JSON往往是更轻量、更现代的选择。但XML在需要注释、严格模式定义(XSD)或与已有XML工具链协作时仍有优势。
核心决策建议 :对于Unity中的游戏数据(如配置表、剧情), 优先选择LINQ to XML 。它在易用性、性能和代码清晰度上取得了最佳平衡。仅当处理 远超内存容量 的巨型XML文件时,才需要考虑
XmlReader。
3. LINQ to XML:现代C#开发者的首选
LINQ to XML
是.NET Framework 3.5引入的,它重新设计了XML对象模型,核心是
XElement
、
XDocument
、
XAttribute
等类。它的设计哲学是“让XML操作像操作普通对象一样简单”,并且完美融入了C#的LINQ查询语法。
3.1 核心对象与基础操作
首先,你需要引入命名空间:
using System.Xml.Linq;
。
创建与加载
// 1. 从字符串创建(最常用,对应Unity的TextAsset)
TextAsset xmlTextAsset = Resources.Load<TextAsset>("Data/Config");
XDocument doc = XDocument.Parse(xmlTextAsset.text);
// 2. 从文件路径加载(适用于StreamingAssets或可写路径)
string filePath = Path.Combine(Application.streamingAssetsPath, "config.xml");
// 注意:在Android上,StreamingAssets路径不能直接用File.Read,可能需要UnityWebRequest
XDocument docFromFile = XDocument.Load(filePath);
// 3. 从头构建一个XML文档
XDocument newDoc = new XDocument(
new XElement("Players",
new XElement("Player",
new XAttribute("id", "1"),
new XElement("Name", "Hero"),
new XElement("Level", 10)
)
)
);
查询数据
这是
LINQ to XML
的精华所在,使用LINQ查询语法或方法语法,直观得像在查询一个集合。
XDocument doc = ...; // 假设已加载上述的`saves.xml`
// 查询单个元素的值
int level1Coins = (int)doc.Root.Element("coins").Element("level_1");
// 注意:直接转换(int)要求值确实是整数,否则用int.Parse()
// 使用LINQ查询所有关卡硬币数大于0的记录
var profitableLevels = from level in doc.Root.Element("coins").Elements()
where (int)level > 0
select new { LevelName = level.Name, Coins = (int)level };
foreach (var level in profitableLevels)
{
Debug.Log($"关卡 {level.LevelName} 有 {level.Coins} 枚硬币");
}
// 方法语法同样强大
var bestTimes = doc.Root.Element("bestTimes")
.Elements()
.ToDictionary(e => e.Name.LocalName, e => (int)e);
// 现在bestTimes是一个字典,键如"level_1",值为150
修改与保存
// 修改现有元素的值
doc.Root.Element("coins").Element("level_1").Value = "5";
// 添加新元素
doc.Root.Element("coins").Add(new XElement("level_5", 10));
// 添加属性
doc.Root.Element("coins").Element("level_1").SetAttributeValue("unlocked", "true");
// 删除元素
doc.Root.Element("coins").Element("level_4").Remove();
// 保存到文件(例如保存到持久化数据路径)
string savePath = Path.Combine(Application.persistentDataPath, "savegame.xml");
doc.Save(savePath);
3.2 在Unity中的实战应用模式
模式一:配置数据管理器
这是最常见的用法。假设我们有一个
ItemConfig.xml
定义游戏物品。
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using UnityEngine;
public class ItemConfigManager : MonoBehaviour
{
private Dictionary<int, ItemData> _itemDict;
[System.Serializable]
public class ItemData
{
public int ID;
public string Name;
public string Description;
public int MaxStack;
public string IconPath;
// 可以从XML属性或子元素解析更多字段...
}
void Awake()
{
LoadItemConfig();
}
void LoadItemConfig()
{
_itemDict = new Dictionary<int, ItemData>();
TextAsset configFile = Resources.Load<TextAsset>("Configs/ItemConfig");
if (configFile == null)
{
Debug.LogError("物品配置文件未找到!");
return;
}
XDocument doc = XDocument.Parse(configFile.text);
// 假设XML结构:<Items><Item id="1"><Name>治疗药水</Name>...</Item></Items>
var items = doc.Root.Elements("Item");
foreach (XElement itemElem in items)
{
ItemData data = new ItemData();
data.ID = (int)itemElem.Attribute("id");
data.Name = itemElem.Element("Name")?.Value ?? "未命名";
data.Description = itemElem.Element("Description")?.Value ?? "";
data.MaxStack = (int?)itemElem.Element("MaxStack") ?? 1; // 使用空值合并运算符提供默认值
data.IconPath = itemElem.Element("Icon")?.Value;
_itemDict[data.ID] = data;
}
Debug.Log($"已加载 {_itemDict.Count} 个物品配置。");
}
public ItemData GetItemData(int id)
{
if (_itemDict.TryGetValue(id, out ItemData data))
return data;
Debug.LogWarning($"未找到ID为 {id} 的物品配置。");
return null;
}
}
模式二:动态本地化系统 XML非常适合存储多语言文本。
<!-- Localization_zh-CN.xml -->
<Localization>
<String key="UI_StartGame">
<Text>开始游戏</Text>
</String>
<String key="Item_Potion_Name">
<Text>治疗药水</Text>
</String>
</Localization>
public class LocalizationManager
{
private Dictionary<string, string> _localizedStrings = new Dictionary<string, string>();
private string _currentLanguage = "zh-CN";
public void LoadLanguage(string langCode)
{
_localizedStrings.Clear();
string path = $"Localization/Localization_{langCode}";
TextAsset asset = Resources.Load<TextAsset>(path);
if (asset == null)
{
Debug.LogError($"本地化文件 {path} 未找到!");
return;
}
XDocument doc = XDocument.Parse(asset.text);
foreach (var strElem in doc.Root.Elements("String"))
{
string key = strElem.Attribute("key")?.Value;
string text = strElem.Element("Text")?.Value;
if (!string.IsNullOrEmpty(key) && text != null)
{
_localizedStrings[key] = text;
}
}
_currentLanguage = langCode;
}
public string GetText(string key)
{
if (_localizedStrings.TryGetValue(key, out string value))
return value;
Debug.LogWarning($"本地化键 '{key}' 在语言 '{_currentLanguage}' 中未找到。");
return $"#{key}#"; // 返回键名作为占位符
}
}
实操心得 :使用
LINQ to XML时,(int?)element或element?.Value这样的空值判断非常有用,可以避免因为XML结构意外变动导致的NullReferenceException。另外,对于配置数据,建议在游戏启动时一次性加载并缓存到字典或列表中,避免在游戏运行时反复解析XML,影响性能。
4. XmlReader与XmlWriter:应对极限场景的利器
当你的XML文件大到几百MB,或者你正在为一个内存紧张的移动平台(如低端安卓设备)开发时,
LINQ to XML
和
XmlDocument
一次性加载整个文档到内存的方式就变得不可行了。这时,你就需要请出
XmlReader
和
XmlWriter
。
4.1 XmlReader:流式读取的“只进扫描器”
可以把
XmlReader
想象成一个只能向前移动的扫描头。它从文件流中逐个读取XML节点(元素、文本、注释等),处理完就丢弃,内存中只保留当前节点的信息。
基础读取模式
using (XmlReader reader = XmlReader.Create(filePath))
{
while (reader.Read()) // 移动到下一个节点
{
switch (reader.NodeType)
{
case XmlNodeType.Element: // 遇到开始标签
Debug.Log($"开始元素: {reader.Name}");
if (reader.HasAttributes) // 读取属性
{
while (reader.MoveToNextAttribute())
{
Debug.Log($" 属性: {reader.Name} = {reader.Value}");
}
reader.MoveToElement(); // 移动回元素
}
break;
case XmlNodeType.Text: // 元素的文本内容
Debug.Log($"文本内容: {reader.Value}");
break;
case XmlNodeType.EndElement: // 遇到结束标签
Debug.Log($"结束元素: {reader.Name}");
break;
}
}
}
实战案例:流式读取巨型地图数据
假设有一个超大的
MapData.xml
,包含成千上万个
<Tile>
元素,每个Tile有坐标和类型。
<Map width="1000" height="1000">
<Tile x="0" y="0" type="Grass"/>
<Tile x="1" y="0" type="Water"/>
<!-- ... 上百万个Tile ... -->
</Map>
用
LINQ to XML
加载这个文件会导致内存爆炸。用
XmlReader
则可以边读边处理。
public void StreamProcessMapData(string filePath, Action<int, int, string> processTileAction)
{
using (XmlReader reader = XmlReader.Create(filePath))
{
while (reader.Read())
{
// 只关注我们需要的Tile元素
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Tile")
{
// 读取属性
int x = int.Parse(reader.GetAttribute("x"));
int y = int.Parse(reader.GetAttribute("y"));
string type = reader.GetAttribute("type");
// 立即处理这个Tile数据,例如生成地图块或存入数据库
processTileAction?.Invoke(x, y, type);
// 因为Tile是空元素(自闭合),所以不需要读取内部文本或寻找结束标签。
// 如果Tile有子元素,需要用reader.Read()或reader.ReadInnerXml()来跳过其内容。
}
// 如果Map元素有属性(如宽高),也可以在这里读取
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "Map")
{
int width = int.Parse(reader.GetAttribute("width"));
int height = int.Parse(reader.GetAttribute("height"));
Debug.Log($"地图尺寸: {width}x{height}");
}
}
}
Debug.Log("地图数据流式处理完毕。");
}
4.2 XmlWriter:高效生成XML
与
XmlReader
对应,
XmlWriter
提供了一种只进的方式写入XML。它比在内存中构建完整DOM树再保存要更节省内存,尤其适合生成大型XML文件。
public void WriteSaveGame(string savePath, PlayerData playerData, List<ItemData> inventory)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // 缩进,让生成的XML更可读
settings.Encoding = Encoding.UTF8;
using (XmlWriter writer = XmlWriter.Create(savePath, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("SaveGame");
// 写入玩家数据
writer.WriteStartElement("Player");
writer.WriteAttributeString("name", playerData.Name);
writer.WriteElementString("Level", playerData.Level.ToString());
writer.WriteElementString("Experience", playerData.Exp.ToString());
writer.WriteEndElement(); // 关闭Player
// 写入背包物品
writer.WriteStartElement("Inventory");
foreach (var item in inventory)
{
writer.WriteStartElement("Item");
writer.WriteAttributeString("id", item.ID.ToString());
writer.WriteString(item.Name); // 写入元素文本内容
writer.WriteEndElement(); // 关闭Item
}
writer.WriteEndElement(); // 关闭Inventory
writer.WriteEndElement(); // 关闭SaveGame
writer.WriteEndDocument();
}
Debug.Log($"存档已写入: {savePath}");
}
注意事项 :使用
XmlReader时,必须非常清楚XML的结构,并精确控制reader的移动。常见的错误是Read()过了头,错过了需要的文本内容,或者没有正确处理空元素和嵌套元素。务必在读取属性后使用MoveToElement()回到元素节点,否则后续读取可能会出错。对于复杂的XML结构,建议先画出一个简单的状态流转图。
5. 经典XmlDocument:为何如今不常用了?
System.Xml.XmlDocument
是.NET Framework 1.1时代就存在的元老,它实现了完整的W3C DOM模型。在
LINQ to XML
出现之前,它是处理XML的主力。
基本用法示例:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlString); // 或 doc.Load(filePath)
// 获取节点
XmlNode root = doc.DocumentElement;
XmlNodeList coinNodes = root.SelectNodes("/saves/coins/*"); // 使用XPath
foreach (XmlNode node in coinNodes)
{
Debug.Log($"{node.Name}: {node.InnerText}");
}
// 修改节点
XmlNode level1Node = root.SelectSingleNode("/saves/coins/level_1");
if (level1Node != null)
{
level1Node.InnerText = "10";
}
// 保存
doc.Save(savePath);
为什么在Unity新项目中不推荐优先使用?
-
API繁琐
:相比于
LINQ to XML直观的链式调用和LINQ查询,XmlDocument的API显得冗长。你需要频繁地类型转换((XmlElement)node),操作属性也需要通过Attributes集合。 -
性能一般
:虽然经过多年优化,但其内存占用和性能通常仍不如
LINQ to XML的轻量级对象模型。 -
XPath的依赖与双刃剑
:
XmlDocument的强大查询依赖于XPath。XPath功能强大,但语法对于简单查询来说过于重量级,且字符串形式的XPath容易写错,不易被C#编译器检查。 -
代码可读性差
:当XML结构复杂时,嵌套的
SelectNodes、ChildNodes遍历会让代码迅速变得难以维护。
它还有用武之地吗?
当然有。如果你需要与一些只接受
XmlDocument
的老旧API交互,或者你非常熟悉XPath并需要执行极其复杂的查询(虽然LINQ to XML的LINQ也能做到),那么
XmlDocument
仍然是可用的选择。但在绝大多数Unity游戏数据处理的场景下,
LINQ to XML
是更优解。
6. 性能实测与内存分析
理论说了很多,我们用一个简单的测试来直观感受一下差异。我们创建一个包含10万个
<Item>
元素的XML文件,分别用三种方式读取并统计时间和内存。
测试代码框架:
using UnityEngine;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using System.IO;
public class XMLPerformanceTest : MonoBehaviour
{
public int itemCount = 100000;
private string _testFilePath;
void Start()
{
_testFilePath = Path.Combine(Application.persistentDataPath, "PerformanceTest.xml");
GenerateTestXmlFile(_testFilePath, itemCount);
TestXmlDocument();
TestLinqToXml();
TestXmlReader();
}
void GenerateTestXmlFile(string path, int count)
{
// 使用XmlWriter快速生成测试文件,避免测试干扰
XmlWriterSettings settings = new XmlWriterSettings { Indent = true };
using (XmlWriter writer = XmlWriter.Create(path, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("Items");
for (int i = 0; i < count; i++)
{
writer.WriteStartElement("Item");
writer.WriteAttributeString("id", i.ToString());
writer.WriteElementString("Name", $"Item_{i}");
writer.WriteElementString("Value", (i * 10).ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
Debug.Log($"测试文件已生成: {path}, 大小: {new FileInfo(path).Length / 1024} KB");
}
void TestXmlDocument()
{
Stopwatch sw = Stopwatch.StartNew();
long memoryBefore = System.GC.GetTotalMemory(false);
XmlDocument doc = new XmlDocument();
doc.Load(_testFilePath);
// 模拟查询:获取所有Item的id和Name
XmlNodeList nodes = doc.SelectNodes("/Items/Item");
foreach (XmlNode node in nodes)
{
string id = node.Attributes["id"].Value;
string name = node.SelectSingleNode("Name").InnerText;
// 这里不进行实际处理,只遍历
}
sw.Stop();
long memoryAfter = System.GC.GetTotalMemory(false);
Debug.Log($"XmlDocument - 耗时: {sw.ElapsedMilliseconds} ms, 内存增量: {(memoryAfter - memoryBefore) / 1024} KB");
}
void TestLinqToXml()
{
Stopwatch sw = Stopwatch.StartNew();
long memoryBefore = System.GC.GetTotalMemory(false);
XDocument doc = XDocument.Load(_testFilePath);
// 使用LINQ查询
var items = doc.Root.Elements("Item")
.Select(e => new {
Id = e.Attribute("id").Value,
Name = e.Element("Name").Value
});
foreach (var item in items)
{
// 遍历
}
sw.Stop();
long memoryAfter = System.GC.GetTotalMemory(false);
Debug.Log($"LINQ to XML - 耗时: {sw.ElapsedMilliseconds} ms, 内存增量: {(memoryAfter - memoryBefore) / 1024} KB");
}
void TestXmlReader()
{
Stopwatch sw = Stopwatch.StartNew();
long memoryBefore = System.GC.GetTotalMemory(false);
int count = 0;
using (XmlReader reader = XmlReader.Create(_testFilePath))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
{
string id = reader.GetAttribute("id");
// 为了获取Name,需要读取到文本节点
reader.ReadToDescendant("Name");
string name = reader.ReadElementContentAsString();
count++;
}
}
}
sw.Stop();
long memoryAfter = System.GC.GetTotalMemory(false);
Debug.Log($"XmlReader - 耗时: {sw.ElapsedMilliseconds} ms, 内存增量: {(memoryAfter - memoryBefore) / 1024} KB, 处理项数: {count}");
}
}
预期结果分析(基于典型情况):
- XmlReader :耗时最短,内存增量几乎可以忽略不计(仅消耗读取缓冲区)。但代码最复杂。
-
LINQ to XML
:耗时略高于
XmlReader,但远低于XmlDocument。内存占用适中,因为它构建了一个对象图,但比XmlDocument的节点树更高效。 - XmlDocument :耗时最长,内存占用最高,因为它要构建最复杂的DOM树结构。
这个测试清晰地展示了在不同数据量级下该如何选择:小数据用
LINQ to XML
享受编码效率,大数据用
XmlReader
保证性能底线。
7. 疑难杂症与避坑指南
在实际项目中,你肯定会遇到一些“奇怪”的问题。这里分享几个我踩过的坑和解决方案。
问题一:在Unity iOS/Android平台上使用LINQ to XML报错或失效? 这通常不是LINQ to XML本身的问题,而是因为:
-
代码剥离(Code Stripping)
:Unity为了减小包体,在发布时会剥离未使用的代码。如果你的项目中没有显式调用
LINQ to XML的某些方法(尤其是在反射或动态调用时),相关代码可能会被错误剥离。 解决方案 :在Project Settings -> Player -> Other Settings中,找到Managed Stripping Level,尝试将其从High降低为Medium或Low,或者为System.Xml.Linq程序集添加链接文件(link.xml)来防止剥离。 - AOT编译限制(仅限IL2CPP) :某些非常复杂的泛型或动态LINQ查询可能在AOT编译时遇到问题。 解决方案 :简化查询,或者确保所有泛型类型在编译时都是确定的。
问题二:读取XML时出现编码错误(乱码)。
XML文件通常以
<?xml version="1.0" encoding="UTF-8"?>
开头声明编码。但有时文件实际编码与声明不符,或者没有BOM头,导致解析错误。
-
解决方案
:确保你的XML文件使用UTF-8编码保存(推荐无BOM)。在C#中,
XDocument.Load和XmlReader.Create默认会尝试检测编码,通常比较可靠。如果遇到问题,可以显式指定编码:using (StreamReader sr = new StreamReader(filePath, Encoding.UTF8)) // 指定编码 { XDocument doc = XDocument.Load(sr); }
问题三:处理包含命名空间(Namespace)的XML时,查询不到节点。 很多Web服务或标准格式(如SVG)的XML带有命名空间。
<root xmlns:ns="http://example.com">
<ns:item>value</ns:item>
</root>
直接用
doc.Root.Element("item")
会返回
null
。
-
解决方案
:使用带命名空间的
XName。
对于XNamespace ns = "http://example.com"; XElement itemElement = doc.Root.Element(ns + "item"); string value = itemElement?.Value;XmlDocument,也需要在XPath中指定命名空间,并使用XmlNamespaceManager。
问题四:XML文件过大,即使是XmlReader也感觉慢。
XmlReader
本身是逐节点读取的,瓶颈可能在IO(磁盘读取速度)。此外,如果对每个节点都进行复杂的字符串处理(如
int.Parse
),也会累积耗时。
-
优化建议
:
- 预处理 :如果可能,将巨型XML拆分成多个小文件。
-
二进制替代
:对于纯粹的游戏数据存储,考虑使用更紧凑的二进制格式(如Protobuf、MessagePack),或Unity的
ScriptableObject资产。 -
异步读取
:使用
XmlReader配合async/await进行异步读取,避免阻塞主线程。public async Task ProcessLargeXmlAsync(string filePath) { using (FileStream stream = File.OpenRead(filePath)) using (XmlReader reader = XmlReader.Create(stream)) { while (await reader.ReadAsync()) { // 处理节点 } } }
问题五:需要频繁修改并保存的XML,如何保证效率?
频繁调用
doc.Save(path)
会进行完整的文件重写,对于大文件是低效的。
-
建议
:
-
对于频繁修改的数据(如游戏存档),考虑在内存中维护一个镜像的数据结构(如
Dictionary、List)。只在游戏退出或特定检查点(Checkpoint)时,将整个数据结构序列化成XML并一次性保存。 - 如果必须增量更新,可以考虑将不同模块的数据存储在不同的XML文件中,减少单次写入的数据量。
-
对于极端性能要求的场景,可以研究使用
XmlWriter直接增量写入特定格式的日志或数据块,但这需要精心设计数据格式。
-
对于频繁修改的数据(如游戏存档),考虑在内存中维护一个镜像的数据结构(如
选择哪种XML处理方式,归根结底是对“开发效率”、“运行时性能”和“内存占用”三者之间的权衡。经过这么多项目的实践,我的个人体会是:
在Unity中,LINQ to XML是处理游戏配置、存档等数据的“甜点”选择
。它用起来顺手,读起来清晰,性能对于99%的场景都足够好。只有当数据量真的突破临界点,或者目标平台是内存捉襟见肘的老年机时,才需要拿出
XmlReader
这把“手术刀”。而
XmlDocument
,就让它留在那些需要与旧世界接口对话的场合吧。最后记住,没有最好的工具,只有最合适的场景。

332

被折叠的 条评论
为什么被折叠?



