IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    云风的 BLOG发表于 2016-06-23 16:42:27
    love 0

    在 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 迭代。



沪ICP备19023445号-2号
友情链接