自己动手实现Lua(四)lua表实现
Lua在5.0版本之前是简单使用哈希表来实现Lua表的,不过由于在实践中数组的使用非常频繁,所以为了专门优化数组的效率,Lua5.0开始改用混合数据结构来实现表。
简单说,这种混合数据结构同事包含了数组和哈希表两部分。如果表的键是连续的正整数,那么哈希表就是空的,值全部按索引存储在数组里。这样,lua数组无论是在空间利用率上(不需要显式存储键),还是在时间效率上(可以按索引存取值,不需要计算哈希码)都和真正的数组相差无几。如果表没有被当成数组使用,那么数据完全存储在哈希表里,数组部分是空的,也没什么损失。
简单实现:
public class LuaTable { public List<LuaValue> arr; public Dictionary<LuaValue,LuaValue> _map; public static LuaTable NewLuaTable(int nArr,int nRec) { LuaTable t = new LuaTable(); //如果参数nArr大于0,说明表可能是当作数组使用的,先创建数组部分; if(nArr > 0) { t.arr = new List<LuaValue>(nArr); } //如果参数nRec大于0,说明表可能是当作记录使用的,先创建哈希表部分。 if (nRec > 0) { t._map = new Dictionary<LuaValue, LuaValue>(nRec); } return t; } /// <summary> /// get()方法根据键从表里查找值。如果键是整数(或者能够转换为整数的浮点数),且在数组索引范围之内, /// 直接按索引访问数组部分就可以了;否则从哈希表查找值。 /// </summary> /// <param name="key"></param> /// <returns></returns> public LuaValue Get(LuaValue key) { int index = KeyToIntger(key); if(index != -1) { if(index >= 1 && index <= arr.Count) { return arr[index - 1]; } } return _map[key]; } public int KeyToIntger(LuaValue key) { key.ConvertToInteger(out long i,out bool b); if(b) { return (int)i; } return -1; } public void Put(LuaValue key,LuaValue val) { if(key.IsNull()) { Console.WriteLine("table index is nil!"); return; } key.ConvertToInteger(out long i,out bool b); int idx = (int)i; if(b && idx >= 1) { if(idx <= arr.Count) { arr[idx - 1] = val; //向数组里放入nil值会制造洞,如果洞在数组末尾的话,调用_shrinkArray()函数把尾部的洞全部删除。 if (idx == arr.Count && val.IsNull()) { ShrinkArray(); } return; } //如果键是整数,而且刚刚超出数组索引范围且值不是nil,就把值追加到数组末尾,然后调用_expandArray()函数动态扩展数组。 if (idx == arr.Count + 1) { if(_map != null) { _map.Remove(key); } if(!val.IsNull()) { arr.Add(val); ExpandArray(); } return; } } //如果值不是nil就把键值对写入哈希表,否则把键从哈希表里删除以节约空间。 if (!val.IsNull()) { if(_map == null) { _map = new Dictionary<LuaValue, LuaValue>(8); } _map[key] = val; } else { _map.Clear(); _map = null; } } public long Len() { return (long)arr.Count; } /// <summary> /// 把尾部的洞全部删除 /// </summary> public void ShrinkArray() { for(int i=0;i<arr.Count;i++) { if(arr[i].IsNull()) { arr.RemoveAt(i); } } } /// <summary> /// 在数组部分动态扩展之后,把原本存在哈希表里的某些值也挪到数组里 /// </summary> public void ExpandArray() { if (_map == null) { return; } //将dict中从当前数组长度+1的连续整数key的value移动到数组部分 //比如数组长度为3 就将字典里key分别为4,5,6,7....的value移动到数组 int index = arr.Count + 1; while (true) { LuaValue key = new LuaValue(index); if (_map.TryGetValue(key, out LuaValue data)) { arr.Add(data); _map.Remove(key); index++; } else { break; } } } }