Unity数据持久化

知识点来源:总结人间自有韬哥在唐老狮,豆包

1.PlayerPrefs

1.1.PlayerPrefs是什么

PlayerPrefs是Unity提供的可以用于存储读取玩家数据的公共类。

1.2.PlayerPrefs的数据存储

PlayerPrefs的数据存储类似于键值对存储,一个键对应一个值。
提供了存储3种数据的方法 int float string。
键: string类型。
值:int float string 对应3种API。

1.3.PlayerPrefs的核心方法

分类 方法 说明
存储数据 SetInt(key, value) 设置由key标识的整数值
存储数据 SetFloat(key, value) 设置由key标识的浮点数值
存储数据 SetString(key, value) 设置由key标识的字符串值
存储数据 Save() 将所有修改的偏好写入磁盘
读取数据 GetInt(key, defaultValue) 返回偏好设置文件中与key对应的值(如果存在),若不存在则返回defaultValue
读取数据 GetFloat(key, defaultValue) 返回偏好设置文件中与key对应的值(如果存在),若不存在则返回defaultValue
读取数据 GetString(key, defaultValue) 返回偏好设置文件中与key对应的值(如果存在),若不存在则返回defaultValue
判断数据是否存在 HasKey(key) 如果key在偏好中存在,则返回true
删除数据 DeleteKey(key) 从偏好中删除key及其对应值
删除数据 DeleteAll() 从偏好中删除所有键和值

2.XML

2.1.读取 XML 文件

操作 关键类/方法 代码示例 说明
创建文档对象 XmlDocument 构造函数 XmlDocument xmlDoc = new XmlDocument(); 初始化 XML 文档对象
加载字符串 XML XmlDocument.LoadXml() xmlDoc.LoadXml(textAsset.text); 从 Resources 加载文本字符串(如 TextAsset
加载路径 XML XmlDocument.Load() xmlDoc.Load(Application.streamingAssetsPath + "/TestXml.xml"); 从 StreamingAssets 加载文件路径(打包后可用)
获取根节点 XmlDocument.SelectSingleNode() XmlNode root = xmlDoc.SelectSingleNode("Root"); 通过 XPath 路径获取根节点(如 "Root"
获取子节点内容 XmlNode.InnerText string name = root.SelectSingleNode("name").InnerText; 获取节点包裹的文本(如 <name>林文韬</name>
获取属性值(中括号) XmlNode.Attributes["属性名"].Value string id = node.Attributes["id"].Value; 通过属性名直接获取属性值(如 id="1"
获取属性值(方法) XmlNode.Attributes.GetNamedItem() string num = node.Attributes.GetNamedItem("num").Value; 通过方法获取属性值(适合动态属性名)
获取同名节点列表 XmlNode.SelectNodes() XmlNodeList friends = root.SelectNodes("Friend"); 获取指定名称的所有子节点(如多个 <Friend>
遍历节点列表 foreach/for 循环 foreach (XmlNode friend in friends) { Debug.Log(friend.SelectSingleNode("name").InnerText);}

2.2.存储 XML 文件

步骤 关键类/方法 代码示例 说明
选择存储路径 Application.persistentDataPath string path = Application.persistentDataPath + "/Player.xml"; 唯一全平台可读可写路径(推荐)
创建文档对象 XmlDocument 构造函数 XmlDocument xmlDoc = new XmlDocument(); 初始化空文档
添加版本声明 XmlDocument.CreateXmlDeclaration() XmlDeclaration dec = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", ""); 添加 XML 头部声明(<?xml version="1.0" encoding="UTF-8"?>
创建根节点 XmlDocument.CreateElement() XmlElement root = xmlDoc.CreateElement("Root"); 创建根节点(如 <Root>
添加子节点及属性 XmlElement.SetAttribute()
保存文件 XmlDocument.Save() xmlDoc.Save(path); 写入磁盘(需权限,persistentDataPath 自动获得)

2.3.修改 XML 文件

操作 关键类/方法 代码示例 说明
判断文件存在 File.Exists() if (File.Exists(path)) { ... } 避免空文件异常
加载现有文件 XmlDocument.Load() XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(path); 读取已存在的 XML 文件
移除节点 XmlNode.RemoveChild()
保存修改 XmlDocument.Save() xmlDoc.Save(path); 覆盖原文件(谨慎操作,建议备份)

2.4.路径选择对比

路径 可读 可写 打包后访问 适用场景
Resources ✔️ ❌(加密) 只读配置(如初始 XML)
StreamingAssets ✔️ ❌(部分平台) ✔️ 只读资源(如内置 XML)
Application.persistentDataPath ✔️ ✔️ ✔️ 玩家存档(推荐)
Application.dataPath ✔️ 编辑器调试(勿用于发布)

3.Json

3.1.JsonUtility

操作 关键类/方法 代码示例 说明
文件存读字符串 File.WriteAllText File.WriteAllText(Application.persistentDataPath + "/Test.json", "存储内容"); 将字符串存储到指定路径的文件中,路径需确保文件夹存在
文件存读字符串 File.ReadAllText string str = File.ReadAllText(Application.persistentDataPath + "/Test.json"); 从指定路径的文件中读取字符串
序列化对象 JsonUtility.ToJson string jsonStr = JsonUtility.ToJson(t); 将类对象序列化为JSON格式的字符串
反序列化对象 JsonUtility.FromJson MrTao t2 = JsonUtility.FromJson(jsonStr, typeof(MrTao)) as MrTao; 将JSON字符串反序列化为类对象

3.2.LitJson

3.2.1.核心方法对比

操作 JsonUtility LitJson 代码示例
序列化对象 JsonUtility.ToJson(对象) JsonMapper.ToJson(对象) json = JsonMapper.ToJson(player);
反序列化对象 JsonUtility.FromJson<T>(json) JsonMapper.ToObject<T>(json) PlayerInfo player = JsonMapper.ToObject(json); // LitJson
序列化集合/数组 需包裹对象(如 [RoleData { List<Role> }] 直接支持 List<T>/T[] List roles = JsonMapper.ToObject<List>(json); // LitJson
序列化字典 不支持(需转换为类) 支持(键建议为 string) Dictionary<string, int> dic = JsonMapper.ToObject<Dictionary<string, int>>(json); // LitJson

3.2.2关键注意事项

场景 JsonUtility LitJson 说明
特性要求 必须 [Serializable] + [SerializeField](私有成员) 无强制特性(私有成员无法序列化) LitJson 仅序列化公共字段
私有成员 [SerializeField] 才能序列化 忽略私有成员(无法序列化) 私有字段直接跳过
字典支持 不支持(需手动转换) 支持(键为 string 最佳) Json 标准键为字符串,数值键会被转为字符串
集合/数组 需包裹对象 直接支持 List<T>/T[] 无需额外包装,与 Json 数组直接对应
null 处理 存储为默认值(如 0/"" 保留 null(需类成员允许) 反序列化 null 时,类成员保持 null(需无参构造)
构造函数 无要求 必须有无参构造函数(否则反序列化报错) 复杂对象需提供无参构造(如 public Class() {}
编码格式 必须 UTF-8 必须 UTF-8 非 UTF-8 编码会导致乱码或加载失败
中文序列化 自动转义(如 \u4E2D 保留原始中文(无需转义) LitJson 默认保留 Unicode 字符,可读性更好

4.二进制

4.1.数据类型 ↔ 字节数组转换

操作 类/方法 代码示例 说明
数值转字节 BitConverter.GetBytes() byte[] bytes = BitConverter.GetBytes(999); 支持 int/float/bool 等,默认小端序(低位在前)
字节转数值 BitConverter.ToXX() int i = BitConverter.ToInt32(bytes, 0); 需指定起始索引,确保字节长度匹配(如 int 需 4 字节)
字符串转字节(UTF-8) Encoding.UTF8.GetBytes() byte[] strBytes = Encoding.UTF8.GetBytes("林文韬"); 中文需用 UTF-8,避免乱码
字节转字符串(UTF-8) Encoding.UTF8.GetString() string str = Encoding.UTF8.GetString(strBytes); 支持指定范围:GetString(bytes, startIndex, length)
注意事项 - 小端序(低位在前)
- 字符串需先存长度(如 int 表示字节数)
网络传输常用大端序,需手动反转字节顺序

4.2.文件操作(File 类)

操作 方法 代码示例 说明
创建文件 File.Create(path) FileStream fs = File.Create(Application.persistentDataPath + "/data.bin"); 创建空文件,路径需含文件名
写入字节 File.WriteAllBytes() File.WriteAllBytes(path, bytes); 覆盖写入,适合小文件
写入文本 File.WriteAllText() File.WriteAllText(path, "文本内容", Encoding.UTF8); 自动处理编码,默认 UTF-8
读取字节 File.ReadAllBytes() byte[] data = File.ReadAllBytes(path); 一次性读取,适合小文件
判断存在 File.Exists(path) if (File.Exists(path)) { ... } 避免操作不存在的文件
删除文件 File.Delete(path) File.Delete(path); 不可恢复,谨慎操作
复制文件 File.Copy(src, dest, true) File.Copy(oldPath, newPath, true); 第三个参数 true 覆盖已存在文件
适用场景 简单文件读写(非流式) 适合配置文件、小数据存储

4.3.文件流操作(FileStream 类)

操作 方法 代码示例 说明
打开/创建流 new FileStream(...) using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {} FileMode.OpenOrCreate 常用,using 自动释放资源
写入字节 fs.Write(bytes, 0, len) fs.Write(BitConverter.GetBytes(strBytes.Length), 0, 4); // 先写字符串长度fs.Write(strBytes, 0, strBytes.Length); // 再写字符串 按顺序写入,需自定义协议(如先长度后内容)
读取字节 fs.Read(buffer, 0, len) byte[] buffer = new byte[4];fs.Read(buffer, 0, 4); // 读 int:int len = BitConverter.ToInt32(buffer, 0);buffer = new byte[len];fs.Read(buffer, 0, len); // 读字符串` 严格按写入顺序读取,避免索引错位
刷新缓冲区 fs.Flush() fs.Flush(); // 强制写入磁盘 防止数据丢失(如程序崩溃前)
关闭流 fs.Close() / using fs.Close(); // 或用 using 块 未关闭可能导致文件被占用,无法删除
适用场景 流式读写(大文件/复杂结构) 适合玩家存档、二进制日志

4.4.文件夹操作(Directory 类)

操作 方法 代码示例 说明
创建文件夹 Directory.CreateDirectory(path) Directory.CreateDirectory(Application.persistentDataPath + "/logs"); 递归创建多级目录(如 /a/b/c
删除文件夹 Directory.Delete(path, true) Directory.Delete(folderPath, true); // 强制删除非空目录 第二个参数 true 递归删除子文件/夹
判断存在 Directory.Exists(path) if (Directory.Exists(folderPath)) { ... } 检查目录是否存在
获取子文件 Directory.GetFiles(folder) string[] files = Directory.GetFiles(folder, "*.bin", SearchOption.AllDirectories); 支持通配符(如 *.bin),SearchOption 控制是否搜索子目录
移动文件夹 Directory.Move(oldPath, newPath) Directory.Move("旧路径", "新路径"); 目标路径不可存在同名目录
目录信息 DirectoryInfo DirectoryInfo di = new DirectoryInfo(folderPath);print(di.FullName); // 全路径print(di.CreationTime); // 创建时间 用于获取详细信息(创建时间、大小等)
适用场景 目录管理、文件组织 适合资源分类、存档管理

4.5.核心注意事项

场景 陷阱 解决方案
字节顺序 小端序(C# 默认)与大端序冲突 网络传输时手动反转字节(如 Array.Reverse(bytes)
字符串存储 未存长度导致读取失败 先存 int 表示字节长度,再存字符串字节数组
文件流未关闭 数据丢失或文件被占用 强制使用 using 块包裹 FileStream
编码格式 中文乱码(非 UTF-8) 统一使用 Encoding.UTF8,避免 ANSI 等本地编码
文件夹删除 非空目录删除失败 传入 true 递归删除(Directory.Delete(path, true)
路径拼接 平台路径格式错误(如 \ vs / 使用 Path.Combine()(如 Path.Combine(folder, "file.bin")

`