序列化是什么?常见的序列化方式有哪些?什么时候我们会用到序列化?
序列化(Serialization)是指将对象的状态信息转换为可以存储或传输的形式(如字节序列、XML 文档、JSON 字符串等)的过程。反序列化则是序列化的逆过程,它将存储或接收到的字节序列、XML 文档、JSON 字符串等转换回对象的状态信息。通过序列化,对象可以在不同的环境中进行持久化存储或网络传输,而反序列化则可以让接收方恢复出原始的对象。
目录
常见的序列化方式
1. JSON 序列化
数据类型支持(JsonUtility不支持循环引用)
LitJson
使用场景
1. 数据存储
2. 网络通信
3. 配置文件
2.XML 序列化
3. 二进制序列化
常见的序列化方式
1. JSON 序列化
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有良好的可读性和跨语言性。它以键值对的形式组织数据,易于人类阅读和编写,也易于机器解析和生成。
Unity 内置了 JsonUtility
类用于简单的 JSON 序列化和反序列化,但功能相对有限。可以使用litJson.(LitJson 功能强大但使用相对复杂,适合处理复杂的数据结构和有特殊需求的场景;而 Unity 内置的 JsonUtility 简单易用,性能较好,适合处理简单的数据结构。与 Unity 内置的 JsonUtility
相比,LitJson 在使用时通常不需要为类添加序列化特性)
数据类型支持(JsonUtility
不支持循环引用)
- 基本数据类型:
JsonUtility
能很好地处理基本数据类型(如果对象之间存在循环引用,JsonUtility
会抛出异常),如int
、float
、string
等。 - 自定义类和结构体:对于自定义类和结构体,类或结构体必须是公共的,且成员变量也必须是公共的,
JsonUtility
才能正确序列化和反序列化。 - 集合类型:可以处理数组和
List<T>
等集合类型,但嵌套集合需要注意结构的正确性。
LitJson
- 强大的类型支持:能处理各种复杂的数据类型,包括嵌套对象、数组、字典等,并且对于自定义类和结构体的处理较为灵活,支持手动实现序列化和反序列化逻辑。
- 自定义序列化:允许开发者自定义序列化和反序列化过程,可通过实现自定义的转换器来满足特殊需求,例如对日期格式的自定义处理。
- 处理循环引用:具备一定的处理对象循环引用的能力,虽然可能需要额外的配置,但能应对复杂对象关系的序列化场景。
使用场景
1. 数据存储
将游戏中的配置数据、玩家进度等存储为 JSON 文件(实现数据持久化),方便后续读取和修改。
2. 网络通信
在网络通信中(在 Unity C# 的网络通信中,Newtonsoft.Json
(Json.NET)是使用次数相对较多的序列化方案),将数据序列化为 JSON 格式进行传输,接收方再进行反序列化。
3. 配置文件
使用 JSON 文件作为游戏的配置文件,方便开发人员修改和管理。
由于使用这部分的功能在游戏开发过程之中可能是经常需要的,所以我们可以把Json的序列化和反序列化制作成Json模块的序列化反序列化功能框架。核心功能代码示例如下:
private static readonly JsonType JsonTypes = JsonType.LitJson;
public static void SaveData(object data, string fileName)
{
string path = UnityEngine.Application.persistentDataPath + "/" + fileName + ".json";
string jsonStr = "";
switch (JsonTypes)
{
case JsonType.JsonUtlity:
jsonStr = JsonUtility.ToJson(data);
break;
case JsonType.LitJson:
jsonStr = JsonMapper.ToJson(data);
break;
}
File.WriteAllText(path,jsonStr);
}
public static T LoadData<T>(string fileName) where T : new()
{
string path = UnityEngine.Application.streamingAssetsPath + "/" + fileName + ".json";
if (!File.Exists(path)) path = UnityEngine.Application.persistentDataPath + "/" + fileName + ".json";
if (!File.Exists(path)) return new T();
string jsonStr = File.ReadAllText(path);
T data = default(T);
switch (JsonTypes)
{
case JsonType.JsonUtlity:
data = JsonUtility.FromJson<T>(jsonStr);
break;
case JsonType.LitJson:
data = JsonMapper.ToObject<T>(jsonStr);
break;
}
return data;
}
2.XML 序列化
XML(eXtensible Markup Language)是一种标记语言,用于存储和传输数据。它具有良好的结构化和扩展性,支持复杂的数据结构和元数据。
xml序列化的使用场景与json的使用场景相似,都是一种数据持久化的序列化方案,我们还可以使用xml制作配置文件书写一些编辑器小工具的功能。例如生成消息类等。
- 若要使用
System.Xml.Serialization.XmlSerializer
对自定义类进行序列化和反序列化,类必须是可序列化的。这意味着类及其所有公共字段都必须是可访问的,并且类要有无参构造函数。如果类包含私有字段,默认情况下这些字段不会被序列化,除非使用特性(如[XmlElement]
)进行标记。 - 可以使用 XML 相关的特性(如
[XmlRoot]
、[XmlElement]
、[XmlAttribute]
等)来控制 XML 元素和属性的生成。例如,[XmlRoot]
可以指定 XML 根元素的名称,[XmlElement]
可以指定 XML 元素的名称,[XmlAttribute]
可以将字段序列化为 XML 属性。 - XML 文件的格式必须严格遵循 XML 规范,否则在解析时会抛出异常。在读取和解析 XML 文件时,要进行异常处理,以确保程序的健壮性。
- 不同平台可能对文件编码有不同的默认设置,在保存和读取 XML 文件时,要明确指定编码格式,以确保数据的正确传输和解析。通常建议使用 UTF - 8 编码。
使用xml制作编辑器小工具的简单案例:自动生成脚本。(展示一部分)
public void GenerateEnum(XmlNodeList list)
{
string namespaceStr = "";
string enumNameStr = "";
string fieldStr = "";
foreach (XmlNode enumNode in list)
{
//获取命名空间配置信息
namespaceStr = enumNode.Attributes?["namespace"].Value;
//获取枚举名配置信息
enumNameStr = enumNode.Attributes?["name"].Value;
XmlNodeList enumFields = enumNode.SelectNodes("field");
//一个新的枚举需要清空上一次拼接的字段字符串
fieldStr = "";
if (enumFields == null)
{
Debug.Log("不存在field字段,请检查您的xml配置文件是否准确!");
return;
}
foreach (XmlNode enumField in enumFields)
{
fieldStr += "\t\t" + enumField.Attributes?["name"].Value;
if (enumField.InnerText != "")
fieldStr += "=" + enumField.InnerText;
fieldStr += ",\r\n";
}
//对所有可变的内容进行拼接
string enumStr = $"namespace {namespaceStr}\r\n" +
"{\r\n" +
$"\tpublic enum {enumNameStr}\r\n" +
"\t{\r\n" +
$"{fieldStr}" +
"\t}\r\n" +
"}";
//保存文件的路径
string path = _saveDataPath + namespaceStr + "/Enum/";
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(path+enumNameStr+".cs",enumStr);
}
Debug.Log("枚举生成结束");
}
private static readonly GenerateCSharp generateCSharp = new();
private static readonly string protoInfoPath = Application.dataPath + "/Scripts/Game/Editor/XmlTool/Net.xml";
[MenuItem("ProtocolTool/生成C#脚本")]
private static void GenerateCSharp()
{
//根据这些信息 拼接字符串 生成对应的脚本
//生成对应枚举脚本
generateCSharp.GenerateEnum(GetNodes("enum"));
//生成对应的数据结构类脚本
generateCSharp.GenerateData(GetNodes("data"));
//生成对应消息类脚本
generateCSharp.GenerateMsg(GetNodes("message"));
//刷新编辑器界面
AssetDatabase.Refresh();
}
3. 二进制序列化
二进制序列化将对象转换为字节序列,这种方式通常比文本格式(如 JSON、XML)更紧凑,读写速度更快,但可读性较差。不同的编程语言有不同的二进制序列化机制。(常用于需要高效存储和传输大量数据的场景,如游戏开发、数据库系统等。)
代码示例:
/// <summary>
/// 在游戏过程中存储的数据
/// </summary>
/// <param name="obj">读取类型</param>
/// <param name="isNetworkTransmission">是否使用网络运输</param>
/// <param name="isEncrypt">是否需要加密数据</param>
/// <typeparam name="T"></typeparam>
public void AutoCreatFile<T>(T obj,bool isNetworkTransmission=false,bool isEncrypt=true) where T:class
{
_key = (byte)UnityEngine.Random.Range(1f, 200f);
string fileName = typeof(T).Name;
if (!Directory.Exists(Application.dataPath + "/BinaryAutoScripts"))
_rootFile = Directory.CreateDirectory(Application.dataPath + "/BinaryAutoScripts");
if (!_passWordDic.ContainsKey(fileName) && isEncrypt)
_passWordDic.Add(fileName,_key);
else
_passWordDic[fileName] = _key;
BinaryFormatter bf = new BinaryFormatter();
if (isNetworkTransmission)
{
using var ms = new MemoryStream();
bf.Serialize(ms, obj);
byte[] bytes = ms.GetBuffer();
if (isEncrypt)
for (int i = 0; i < bytes.Length; i++)
bytes[i] ^= _key;
File.WriteAllBytes(Application.dataPath + $"/BinaryAutoScripts/{fileName}_Net.nicolepotter", bytes);
}
else
{
using var fs = new FileStream(Application.dataPath + $"/BinaryAutoScripts/{fileName}.nicolepotter", FileMode.OpenOrCreate, FileAccess.Write);
bf.Serialize(fs, obj);
fs.Flush();
}
#if UNITY_EDITOR
AssetDatabase.Refresh();
#endif
}