转载

正确的序列化 Lua 中带元表的对象

在 Lua 5.2 之后的版本,约定了在元表中可以给出一个 __pairs 方法,而 lua 的基础库 pairs 会使用这个元方法来迭代一个对象。

Lua 5.3 之后的版本,取消了 lua 5.2 中的 __ipairs 约定,而统一使用 lua_geti 来访问整数为索引的数组。

可惜的是,许多 lua 序列化库对此支持的并不好。今天我在改进 bson 的序列化库时,重新考虑了这个问题,看看这个序列化过程怎么做,才能更好的支持 lua 5.3 以后的约定。

在 skynet 中重新实现的 bson 库是这样做的:

和 json 不同,bson 在规范中严格区分了数组和字典。

那么,序列化时,应首先判断一个 table 是否有 __len 元方法,如果有,则表示它是一个数组。因为我们不支持把一个需要传入 bson 序列化的 table 同时当成数组和字典使用,如果实现了 __len 元方法,那么显然是希望把它看作一个数组。

然后,判断这个 table 是否有 __pairs 元方法。如果有,这表示它是一个字典,需要用这个元方法迭代它。

如果两个元方法都没有,那么这个 table 是个原生表,需要用额外的方法探测它到底是一个数组还是一个字典。这里采用的方法是,使用 lua_rawlen 获取一下数组部分的长度。然后调用 lua_next 传入最后一个数字索引,探测是否有其它的 key 。

这种方法在 lua 的文档中并没有严格约定,它依赖 lua 的实现。目前官方 lua 的实现中, lua_next 总是先迭代完数组部分,再迭代 hash 部分的。这样实现最为便捷,所以看起来也不会修改。而严格的检测方法则应该是用 lua_next 迭代所有的 key ,逐个判断它们都是否是数字,且是否连续。我不想采用这种开销 O(n) 的严格算法,前面提到的 O(1) 的取巧方法实际工作的很好。

这种,我们对 table 分了三种类别:数组、带元表的字典、原生字典。

数组这个类型不必区分是原生数组还是带元表的,而只需要取出长度(使用 __len 或调用 lua_rawlen ),然后用 lua_geti 逐个调用。如果发现数组中有空洞(value 为 nil ),序列化过程会抛出 error 。(这点对之前的实现是一个改进,老的版本并不能检测出数组中的空洞)

带元表的字典采用 __pairs 方法进行迭代,而原生字典可以直接用 lua_next 迭代。

原文  http://blog.codingnow.com/2016/06/seri_lua_object.html
正文到此结束
Loading...