问题描述
近期在项目制作中,需要运用Asset进行数据结构的实例化存储和使用,在制作编辑器的过程中发现一个十分尴尬的问题,在某些情况下会发生代码修改后通过编辑器配置的某些数据会丢失,我把这种问题叫做“序列化数据丢失”(因为我也不知道怎么称呼它)。
通过一个简单的示例可以体现出该问题:
public string[][] twoString = new string[1][] ;
以上为一个二维数组的结构体,对其进行赋值后保存成Asset文件。在不改动源代码的情况下,配置进入的数据可以正常的读取并且不会丢失,但当我们对任意文件进行代码修改并保存后(工程对代码进行重新编译)。再次读取该Asset文件会发现数据全部丢失。
问题测试
我对该问题进行了各种可能性的测试寻找会出现序列化数据冲刷现象的情况,如下数据测试用例:
// 失败的方式
public TDictionary<string, string> testT=new TDictionary<string, string>();
// 正确的方式
public TestDictionary testDictionary = new TestDictionary();
// 正确的方式
public List<string> testString = new List<string>();
// 失败的方式
public string[][] twoString = new string[1][] ;
// 正确的方式
public DepthDictionary depth = new DepthDictionary();
// 失败的方式
public DepthErrDictionary depthErr = new DepthErrDictionary();
其中TDictionary使用的是第三方编写的继承自IDictionary实现的字典数据结构,该第三方框架地址: https://github.com/neuecc/SerializableDictionary
Depth相关数据结构:
[Serializable]
public class PathItem
{
public PathItem(string path)
{
this.path = path;
}
// 路径
public string path;
}
[Serializable]
public class PathsList
{
public PathsList(string path)
{
psList.Add(new PathItem(path));
}
public List<PathItem> psList = new List<PathItem>();
}
[Serializable]
public class DepthDictionary : AssetDictionary
{
public PathsList[] list;
public DepthDictionary()
{
list = new PathsList[MAX];
}
protected override T[] GetValues<T>()
{
return list as T[];
}
protected override void SetValues<T>(T[] values)
{
list = values as PathsList[];
}
}
[Serializable]
public class DepthErrDictionary : AssetDictionary
{
public List<PathItem>[] list;
public DepthErrDictionary()
{
list = new List<PathItem>[MAX];
}
protected override T[] GetValues<T>()
{
return list as T[];
}
protected override void SetValues<T>(T[] values)
{
list = values as List<PathItem>[];
}
}
问题解决
通过测试用例大体总结出来的会导致数据冲刷的现象出现在如下几点:
1. 泛型类的使用,但是注意如果直接使用List<T>是不会发生问题,注:T需满足当前所述几点条件。
2. 嵌套的数组结构,无论是[][]还是List<T>[]都会产生问题。
3. 不可序列化的数据结构(如Dictionary)。
所以遵循以上几点去实现数据结构将规避该问题的发生,其中一个重要的是Dictionary的支持,以及多维数组的支持。
多为数组的方案在上述测试DepthDictionary中应用并演示,使用嵌套对象的方式解决,结果如图:
提供一份简单的Dictionary实现例子,本例采用哈希冲突的线性探测方式实现,以供参考。
public abstract class AssetDictionary
{
protected string[] keys;
// 线性探索表的块大小
protected int M = 16;
// 数据块幂数
protected const int N = 8;
protected int ADDI = 0;
// 2^N
protected int MAX;
protected int KEYS_COUNT = 0;
protected int lenght = 0;
public int Count {
get { return lenght; }
}
protected abstract T[] GetValues<T>();
protected abstract void SetValues<T>(T[] values);
public AssetDictionary()
{
MAX = (int)Math.Pow(2, N);
keys = new string[MAX];
}
public void Add<T>(string key,T value)
{
int index = key.GetHashCode() % MAX;
// **数据
for (int i = 0; i < M; i++)
{
if (index == MAX)
index = 0;
if (keys[index] != null && keys[index] != key)
continue;
keys[index] = key;
GetValues<T>()[index] = value;
lenght++;
return;
}
// 重构数据块大小
resetBlock<T>(null,null);
Add<T>(key, value);
}
public bool Remove<T>(string key)
{
int index = key.GetHashCode() % MAX;
// **数据
for (int i = 0; i < M; i++)
{
if (index == MAX)
index = 0;
if (keys[index] == key)
{
keys[index] = null;
GetValues<T>()[index] = default(T);
lenght--;
return true;
}
}
return false;
}
public T GetValue<T>(string key)
{
int index = key.GetHashCode() % MAX;
if(keys[index] == null)
return default(T);
// **数据
for (int i = 0; i < M; i++)
{
if (index == MAX)
index = 0;
if (keys[index] == key)
return GetValues<T>()[index];
}
return default(T);
}
protected void resetBlock<T>(string[] copyKeys,T[] copyValues)
{
// 增加新的区块
ADDI += 1;
MAX = (int)Math.Pow(2, N + ADDI);
M = (int)Math.Pow(2, 4 + ADDI);
// 原始数据空间
if(copyKeys == null)
copyKeys = keys;
if(copyValues == null)
copyValues = GetValues<T>();
// 覆盖重置空间
keys = new string[MAX];
SetValues<T>(new T[MAX]);
// 重构空间,根据目前持有的key进行
for(int i = 0; i < copyKeys.Length; i++)
{
if(copyKeys[i] != null)
{
if(AddBlockValue<T>(copyKeys[i], copyValues[i]) == false)
{
// 列表超出最大范围
resetBlock<T>(copyKeys,copyValues);
return;
}
}
}
}
protected bool AddBlockValue<T>(string key,T value)
{
int index = key.GetHashCode() % MAX;
// **数据
for (int i = 0; i < M; i++)
{
if (index == MAX)
index = 0;
if (keys[index] != null)
continue;
keys[index] = key;
GetValues<T>()[index] = value;
return true;
}
return false;
}
}
使用中需要继承AssetDictionary并实现如上边提到的数据结构中Depth相关的代码。