说起数据保存与加载,过去开发过程中直接想到的不是xml,json,sqlite就是csv这些可以直接在外部进行编辑的数据保存方式。实际软件中需要大量用户数据保存,而并不用直接用其他软件打开,只需要专用的软件也就是开发者自己做的界面显示。
这种情况下,才突然回过神来,原来以前做的设置信息,用户使用过程中的缓存点这些基本的信息我居然用那么多浪费资源的保存方式。早用二进制序列化和反序列化不就好了,少了多少事。
我所说的序列化具体可以实现什么样的效果呢,其实就是可以将System.Serializable支持的类等直接转换为二进制数据保存到到本地文件,再需要的时候再反序列化成实例类。试了试,类其中一般的集合和其他类都可以很好的进行序列化。
一、需要保存的对象
[System.Serializable]
public class Temp
{
public string id;
public Dictionary<string,string> expDic;
}
这样一个类所创建出来的实例,在json或其他软件进行编辑没有必要的情况下。可将其实例对象保存为二进制,注意要加【System.Serizlizable】!
二、如何保存
在给定的文件路径下,可以创建一个如下的类,来保存任何和个数据类。
using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
public class ClassCacheSaver {
FileStream fileStream;
private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter();
public ClassCacheSaver(string fileName){
fileStream = new FileStream(fileName,FileMode.OpenOrCreate);
}
private byte[] Serialize<T>(T obj) where T : class
{
try
{
using (var memoryStream = new MemoryStream())
{
_binaryFormatter.Serialize(memoryStream, obj);
return memoryStream.ToArray();
}
}
catch (Exception e)
{
UnityEngine.Debug.Log(e);
return null;
}
}
private void WriteLength(int len)
{
var lenbuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(len));
fileStream.Seek(0, SeekOrigin.End);
fileStream.Write(lenbuf, 0, lenbuf.Length);
}
private void WriteObject(byte[] data)
{
fileStream.Seek(0, SeekOrigin.End);
fileStream.Write(data, 0, data.Length);
}
private void Dispose()
{
fileStream.Flush();
fileStream.Dispose();
fileStream.Close();
}
public void WriteObject<T>(T obj) where T:class
{
fileStream.SetLength(0);
var data = Serialize(obj);
WriteLength(data.Length);
WriteObject(data);
Dispose();
}
}
三,如何反序列化
和保存一样,需要链接到数据文件,并读取。注意的是,FileStream使用后需要进行Close。值得注意的是,因为实际过程中,并不会有需要频繁的序列化和反序列化,这里设计其使用之后就Dispose,用的时候在创建。
using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
public class ClassCacheReader{
private FileStream fileStream;
private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter();
const int lensize = sizeof(int);
public ClassCacheReader(string fileName)
{
fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
}
private int ReadLength()
{
fileStream.Seek(0, SeekOrigin.Begin);
var lenbuf = new byte[lensize];
var bytesRead = fileStream.Read(lenbuf, 0, lensize);
if (bytesRead == 0)
{
return 0;
}
if (bytesRead != lensize)
throw new IOException(string.Format("Expected {0} bytes but read {1}", lensize, bytesRead));
return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenbuf, 0));
}
private T ReadObject<T>(int len) where T:class
{
fileStream.Seek(lensize, SeekOrigin.Begin);
var data = new byte[len];
fileStream.Read(data, 0, len);
Dispose();
using (var memoryStream = new MemoryStream(data))
{
return (T)_binaryFormatter.Deserialize(memoryStream);
}
}
private void Dispose()
{
fileStream.Flush();
fileStream.Dispose();
fileStream.Close();
}
public T ReadObject<T>() where T : class
{
var len = ReadLength();
return len == 0 ? default(T) : ReadObject<T>(len);
}
}
四、接口设计
在保存和加载者实现的基础上,考虑到这个功能的易用性,可以设计如下的使用控制器:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class ClassCacheCtrl
{
string saveFolder;
ClassCacheSaver saver;
ClassCacheReader reader;
public ClassCacheCtrl(string saveFolder)
{
this.saveFolder = saveFolder;
}
public void SaveClassToLocal<T>(T arg0) where T : class
{
string fileName = Path.Combine(saveFolder, typeof(T).ToString()) + ".bin";
saver = new ClassCacheSaver(fileName);
saver.WriteObject(arg0);
}
public T LoadClassFromLocal<T>() where T : class
{
string fileName = Path.Combine(saveFolder, typeof(T).ToString()) + ".bin";
reader = new ClassCacheReader(fileName);
return reader.ReadObject<T>();
}
}
五、在Unity3d中测试(其他开发者忽略)
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class Temp
{
public string id;
public Dictionary<string,string> expDic;
}
public class LocalTest : MonoBehaviour {
ClassCacheCtrl ctrl;
Temp temp;
void Start()
{
ctrl = new ClassCacheCtrl(Application.dataPath);
temp = new Temp();
temp.id = "1";
temp.expDic = new Dictionary<string, string>();
temp.expDic.Add("key","value");
}
void OnGUI()
{
if (GUILayout.Button("保存temp到本地"))
{
ctrl.SaveClassToLocal(temp);
Debug.Log("保存->\n");
}
if (GUILayout.Button("从本地加载"))
{
Temp temp = ctrl.LoadClassFromLocal<Temp>();
Debug.Log("加载->\n");
if (temp != null)
{
Debug.Log(temp.id);
Debug.Log(temp.expDic["key"]);
}
}
}
}
运行效果如下:
输出:
数据文件: