自己实现lua table

游戏存盘的时候经常需要将一个lua table序列化为一个字符串,方便读取。序列化是一个蛮耗CPU的操作,因此如果可以将这部分的工作单独为一个线程,就能显著降低主工作线程的消耗,提高效率。本系列文章介绍用一个自己实现的数据结构lighttable来替代lua 原生的table,通过自己管理这个数据结构,来实现序列化多线程。
1. lua table简介及其原理
      用过lua的人都知道,table可以用做array,也可以用来做map。table使用非常方便,并且效率极佳。网上有大量的对table的源码的分析。这里简要概括一下,table之所以能实现即是array,又是map,是因为table内部既包含了一个array部分,也包括了一个map部分。number类型的key,可能会落入到array部分,也可能会落入到map部分,而非number类型的key,必定是落入到map部分。
2. 接口设计
      知道了table的原理,想要自己实现一个,也并不太难。开始考虑也像table一样,实现一个array部分和map部分的混合体,后面做了第一个版本出来,发现要把array部分实现得跟lua一样高效,比较复杂,不好控制。于是就做这样的处理,只支持number和string为key,无论是number key,还是string key,全部存入map部分。接口如下:
01 classtable {
02 public:
03 table();
04 ~table();
05
06 intgetref();//获取引用计数
07 voidgrab();//增加引用计数
08 void_release();//减少引用计数
09 intget_type(constchar*key, size_tsz_idx, uniontable_value *v); //获取指定索引的type
10 voidvalue_string(uniontable_value *v, table_setstring_func sfunc, void*ud);
11
12 size_tsize();//table的size
13 size_tkeys(structtable_key *tk, size_tcap);//table所有key, 返回值为key的数目,所有的key存在tk中
14
15 //type getter
16 doubleget_number(constchar*key, size_tsz_idx);
17 intget_boolean(constchar*key, size_tsz_idx);
18 uint64_t get_ud(constchar*key, size_tsz_idx);
19 char* get_string(constchar*key, size_tsz_idx);
20
21 //type setter
22 intset_number(constchar*key, size_tsz_idx, doublen);
23 intset_boolean(constchar*key, size_tsz_idx, intb);
24 intset_ud(constchar*key, size_tsz_idx, uint64_t ud);
25 intset_string(constchar*key, size_tsz_idx, constchar*str, size_tsz);
26
27 //序列化与反序列化函数
28 intserialize();//返回非0表示错误,否则成功
29 constchar* unserialize(); //将table序列化为一个string返回
30 char*get_serialed();
31
32 private:
33 //data members
34 intref;
35 intlock;
36 intmap_lock;
37
38 lmap *map;
39 //private functions
40 ......
41 }
可以看出来,table支持4中类型的数据为value,number,boolean,string不用说,ud表示lua里面的userdata,可以是复合数据结构。setter系列函数的前两个参数,用来描述key,如果key为string,那么第一个参数key就是该string,第二个参数表示其长度。否则,key为NULL,第二个参数表示真正的number key。
3. 实现细节
      说是实现细节,其实还是跟接口有关。来看下set_number的实现流程:
01 inttable::_insert_table(constchar*key, size_tsz_idx, structvalue *v)
02 {
03 _table_lock();
04 inttype = _insert_map_value(key, sz_idx, v);
05 _table_unlock();
06
07 returntype;
08 }
09
10 //返回0表示成功,非0反之
11 inttable::set_number(constchar*key, size_tsz_idx, doublen)
12 {
13 structvalue tmp;
14 tmp.type = LT_NUMBER;
15 tmp.v.n = n;
16 inttype = _insert_table(key, sz_idx, &tmp);
17
18 returntype != LT_NUMBER && type != LT_NIL;
19 }
      所有的setter函数,最终都会走到_insert_table这个函数,所以问题的关键在于map的实现。在这个实现中,我采用的链接方式的hash表。
       这个结构有个好处,通过调整每条单链表的最大深度,以及索引的数目,可以很方便的调整hash的哈希效率(insert), 查找效率(search),针对性能做出优化。
_insert_map_value这个函数,会调用到map的insert函数。那么,来看看该函数的实现:
01 intlmap::_insert_value(constchar*key, size_tsz, structvalue *v, int&depth)
02 {
03 uint32_t h = hash(key, sz, size);
04 structnode **pn = &data[h];
05 while(*pn != NULL) {
06 structnode *tmp = *pn;
07 if(_is_in_hash(key, sz, tmp)) {
08 inttype = tmp-<v.type;
09 tmp-<v.v = v-<v;
10 returntype;
11 }
12 pn = &tmp-<next;
13 ++depth;
14 }
15
16 structnode *n = _new_node(key, sz, v-<type);
17 n-<v = *v;
18 n-<v.ref = 1;
19 *pn = n;
20
21 index += 1;
22
23 returnLT_NIL;
24 }
      这个是一个经典的hash链表的插入过程。首先计算出链表的索引(第一句),接着找到索引对应的链表的头地址(第二句),如果链表不为空,那么,将新的键值对插入到链表的头部,这个过程中如果发现原来的key在链表中已经存在,就更新其对应的value(while循环里面的if部分),否则,插入。如果链表为空,则构造一个新的节点,插入到链表的头。
4.未完待续
      上面就简单介绍一个类lua table的原理,接口设计及实现上的一些细节。如何把这个table结构导入到脚本,并且让脚本能方便的使用,是另一个复杂的问题,欲知后事如何,且看下一篇文章咯。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值