說起數據保存與加載,過去開發過程中直接想到的不是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"]);
}
}
}
}
運行效果如下:
輸出:
數據文件: