lua table源码学习

刚从超市回来,吃了一包樱桃很满足,哈哈,就接着跟着云大哥看看lua table部分的源码:

table是lua里唯一暴漏给client的数据结构,肯定是大神们经过精心设计的。对于使用者简单易用,木有STL那么多容器,那table是如何被操作呢,我不怎么喜欢一开始就问“table是如何实现”,因为了解了使用场景才有兴趣了解它的内部构造。

LUA_API void lua_createtable (lua_State *L, int narray, int nrec) {
  Table *t;
  lua_lock(L);
  luaC_checkGC(L);
  t = luaH_new(L);
  sethvalue(L, L->top, t);
  api_incr_top(L);
  if (narray > 0 || nrec > 0)
    luaH_resize(L, t, narray, nrec);
  lua_unlock(L);
}

lua提供lua_createtable这个API,C里可以调用这个API创建table:

里面声明了Table这个数据结构,然后锁定luaState,检查GC,然后创建table,放进stack,解锁。

首先来看Table这个数据结构:

typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
  lu_byte lsizenode;  /* log2 of size of `node' array */
  struct Table *metatable;
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  GCObject *gclist;
  int sizearray;  /* size of `array' array */
} Table;
lsizenode是node节点数目的对数;sizearray表示array数组的数目。

大家肯定还不怎么清楚Node是什么,以及用来做什么的:

typedef struct Node {
  TValue i_val;
  TKey i_key;
} Node;

是包含了key,value的结构。

而其实

TValue是指 Value value_; int tt_的数据结构;

而TKey是指

typedef union TKey {
  struct {
    TValuefields;
    struct Node *next;  /* for chaining */
  } nk;
  TValue tvk;
} TKey;

的数据结构。

1、然后我们看如何创建table数据结构:

Table *luaH_new (lua_State *L) {
  Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h;
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

首先申请内存,把相关域初始化,这个setnodevector是做什么呢?

static void setnodevector (lua_State *L, Table *t, int size) {
  int lsize;
  if (size == 0) {  /* no elements to hash part? */
    t->node = cast(Node *, dummynode);  /* use common `dummynode' */
    lsize = 0;
  }
  else {
    int i;
    lsize = luaO_ceillog2(size);
    if (lsize > MAXBITS)
      luaG_runerror(L, "table overflow");
    size = twoto(lsize);
    t->node = luaM_newvector(L, size, Node);
    for (i=0; i<size; i++) {
      Node *n = gnode(t, i);
      gnext(n) = NULL;
      setnilvalue(gkey(n));
      setnilvalue(gval(n));
    }
  }
  t->lsizenode = cast_byte(lsize);
  t->lastfree = gnode(t, size);  /* all positions are free */
}

如果是size是0,就只是设置为dummynode;

否则检测长度是否过大,接着为将node节点链接到新分配的vector上,这里会分配size个node;然后就是遍历node vector作初始化,最后设置lsizenode大小和当前freenode。

2、那如何删除table呢?(C语言一定要记得哟,不然会泄,哈哈),lua代码风格很好,对于函数命名很规范,比如申请内存是luaH_new,那释放内存就会死luaH_free:

void luaH_free (lua_State *L, Table *t) {
  if (!isdummy(t->node))
    luaM_freearray(L, t->node, cast(size_t, sizenode(t)));
  luaM_freearray(L, t->array, t->sizearray);
  luaM_free(L, t);
}

当然如果是dummy就没有node数组,不是才要先释放之前分配的node数组,然后释放array数组,最后释放table内存。

3、table使用时怎么实现的呢?

应该有set,get方法。嗯,那是一定的,首先看下如何set的:

LUA_API void lua_rawset (lua_State *L, int idx) {
  StkId t;
  lua_lock(L);
  api_checknelems(L, 2);
  t = index2addr(L, idx);
  api_check(L, ttistable(t), "table expected");
  setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);
  invalidateTMcache(hvalue(t));
  luaC_barrierback(L, gcvalue(t), L->top-1);
  L->top -= 2;
  lua_unlock(L);
}

我忽略了细节,直接看lua内部如何设置key,value的:

/*
** beware: when using this function you probably need to check a GC
** barrier and invalidate the TM cache.
*/
TValue *luaH_set (lua_State *L, Table *t, const TValue *key) {
  const TValue *p = luaH_get(t, key);
  if (p != luaO_nilobject)
    return cast(TValue *, p);
  else return luaH_newkey(L, t, key);
}

这里会首先查询,如果查到就直接返回value,还是调用luaH_get。

没有则新建一个key;

/*
** main search function
*/
const TValue *luaH_get (Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key));
    case LUA_TNIL: return luaO_nilobject;
    case LUA_TNUMBER: {
      int k;
      lua_Number n = nvalue(key);
      lua_number2int(k, n);
      if (luai_numeq(cast_num(k), n)) /* index is int? */
        return luaH_getint(t, k);  /* use specialized version */
      /* else go through */
    }
    default: {
      Node *n = mainposition(t, key);
      do {  /* check whether `key' is somewhere in the chain */
        if (luaV_rawequalobj(gkey(n), key))
          return gval(n);  /* that's it */
        else n = gnext(n);
      } while (n);
      return luaO_nilobject;
    }
  }

从注释可以看出luaH_get是主要的table的搜索函数。这里会根据可以的数据类型分别查找。

因为lua中lua_TValue有个int型的tt字段表示数据类型。

可以看到有 短字符串 空类型 数字 其他 四种分类查询,那我们分别看看:

1)查询字符串

/*
** search function for short strings
*/
const TValue *luaH_getstr (Table *t, TString *key) {
  Node *n = hashstr(t, key);
  lua_assert(key->tsv.tt == LUA_TSHRSTR);
  do {  /* check whether `key' is somewhere in the chain */
    if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key))
      return gval(n);  /* that's it */
    else n = gnext(n);
  } while (n);
  return luaO_nilobject;
}

首先通过hash拿到一个node桶,然后遍历桶里的node,如果key是字符串,且key完全相同就返回value,否则返回nil。

2)数字key查询:

/*
** search function for integers
*/
const TValue *luaH_getint (Table *t, int key) {
  /* (1 <= key && key <= t->sizearray) */
  if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray))
    return &t->array[key-1];
  else {
    lua_Number nk = cast_num(key);
    Node *n = hashnum(t, nk);
    do {  /* check whether `key' is somewhere in the chain */
      if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
        return gval(n);  /* that's it */
      else n = gnext(n);
    } while (n);
    return luaO_nilobject;
  }
}

如果key在table的数组表示范围内,就直接返回下标,这里看下具体计算的时候要key-1,所以清楚了客户调用lua table的时候要从1开始,0是非法的。

如果没在数组部分就肯定在散列表,因为table只有这两个部分在存储key,value;还是先散列,然后遍历查询,跟之前的方法一样,我在想 这些方法用C++写,肯定会写成模板,哈哈。

3)然后也是拿到hash值,遍历查询。

那新建key都做了写什么工作呢?

/*
** inserts a new key into a hash table; first, check whether key's main
** position is free. If not, check whether colliding node is in its main
** position or not: if it is not, move colliding node to an empty place and
** put new key in its main position; otherwise (colliding node is in its main
** position), new key goes to an empty position.
*/
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
  Node *mp;
  if (ttisnil(key)) luaG_runerror(L, "table index is nil");
  else if (ttisnumber(key) && luai_numisnan(L, nvalue(key)))
    luaG_runerror(L, "table index is NaN");
  mp = mainposition(t, key);
  if (!ttisnil(gval(mp)) || isdummy(mp)) {  /* main position is taken? */
    Node *othern;
    Node *n = getfreepos(t);  /* get a free place */
    if (n == NULL) {  /* cannot find a free place? */
      rehash(L, t, key);  /* grow table */
      /* whatever called 'newkey' take care of TM cache and GC barrier */
      return luaH_set(L, t, key);  /* insert key into grown table */
    }
    lua_assert(!isdummy(n));
    othern = mainposition(t, gkey(mp));
    if (othern != mp) {  /* is colliding node out of its main position? */
      /* yes; move colliding node into free position */
      while (gnext(othern) != mp) othern = gnext(othern);  /* find previous */
      gnext(othern) = n;  /* redo the chain with `n' in place of `mp' */
      *n = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
      gnext(mp) = NULL;  /* now `mp' is free */
      setnilvalue(gval(mp));
    }
    else {  /* colliding node is in its own main position */
      /* new node will go into free position */
      gnext(n) = gnext(mp);  /* chain new position */
      gnext(mp) = n;
      mp = n;
    }
  }
  setobj2t(L, gkey(mp), key);
  luaC_barrierback(L, obj2gco(t), key);
  lua_assert(ttisnil(gval(mp)));
  return gval(mp);
}

这个函数稍复杂。

1、检测是否为nil,是则报错返回;如果是数字,若是未定义数字也错误返回

2、获取key的主位置,内部就是根据key类型不同获取hash值

3、如果主位置被占,则获取table node表的一个空闲位置

若没有空闲位置,则增大table,然后重新设置

若有空闲位置,检测主位置上的node是否鸠占鹊巢,如果是,就给这丫分配一个适当位置;

如果不是,就是要解决冲突,放在下一个就行了;

最后返回value。

ok,暂时分析到这里吧。表示lua真的很流弊。


参考 云风《lua源码赏析》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值