« skynet 入门指南 | 返回首页 | skynet 的一个简单范例 »

正确的序列化 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 迭代。

Comments

sb

可以 email 联系我。

我是出版社工作人缘,<<lua程序设计>>第四版英文版已经上市,不知云风大侠是否有兴趣为LUA社区做点贡献翻译下该书呢?

如果数组有空洞,lua_rawlen返回的值不确定,这样的话你这个方法貌似不行吧?

哦,那这就是 Lua 的惯例了。好久不写 Lua,完全没感觉了……

@依云

因为对 table 取 # 操作,按 lua 的惯例就是认为 table 是一个 sequence ,取这个 sequence 的长度。

固然你可以用 `__len` 做其它用途,正如你也可以把 `__add` 这些做加法以外的用途,但并没有什么好处。

使用 `__len` 用于模拟一个 sequence 是 lua 基础库已经采用的方案。 如果你真的用 `__len` 记录字典中有多少个元素,就好比你实现 `__add` 不做加法而去做减法。

即没有获得什么好处,还会使 lua 标准库无法正常工作。

table.concat / insert / move / remove / sort / unpack 均依赖 `__len` 的正确定义。

我记错了,json 也区分了数组。

为什么「实现了 __len 元方法,那么显然是希望把它看作一个数组」呀?如果这个 __len 是记录这个字典中有多少个元素呢?

同问为什么说在 JSON 中没有区分数组和字典?

json 也是严格区分了数组和字典的吧。

我觉得 lua 唯一不好的地方就是不区分数组和字典。它带来的不必要的复杂度。

当然所有 luaer 认为这是侁点。

Post a comment

非这个主题相关的留言请到:留言本