Lua5.2.3源码阅读(2)-Table

table在lua中是一个重要的数据结构,使用起来非常灵活,可以用它表示通常的array,map,set等结构。在分析源码之前,先看看table的一些特性。
(1). table是个key-value数组,索引key可以是数字,也可以是字符串。
(2). Key为数字时,默认下表从1开始(c/c++从0开始)
(3). table大小可以动态变化
(4). Lua5.2.3中table有7个成员函数(concat,insert,move,pack,remove,sort,unpack)

  1. Lua中table定义如下(lobject.h)。
typedef union TKey {
  struct {
    TValuefields;
    struct Node *next;  /* for chaining */
  } nk;
  TValue tvk;
} TKey;
typedef struct Node {
  TValue i_val;
  TKey i_key;  //保存有下一个指针
} Node;
typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
  lu_byte lsizenode;  /* log2 of size of `node' array hash的大小(为2的整数次幂,这里表示的是幂次)*/
  struct Table *metatable;  /* 元表 */
  TValue *array;  /* array part 数组部分*/
  Node *node;     //hash表
  Node *lastfree;  /* any free position is before this position */
  GCObject *gclist;
  int sizearray;  /* size of `array' array 数组的长度信息*/
} Table;

Commonheader如上一篇介绍,包含链表指针,数据类型,回收状态。Table分为连个部分,数组部分和key-value部分。数组部分不需要保存键值,这部分在底层实现,是使用者看来只是透明的。在往表中插入数值时,table会根据key-value当前的状态动态地使用这两个部分。数组部分保存的是索引 (0

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 key为数组情况 */
      /* 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;
    }
  }
}

(1) 当查找的key是字符串时,具体有luaH_getstr函数执行。该函数首先计算字符串的hash值,然后将hash值通过lmod方法映射到hash表中的位置,再在该位置的node*链表中查找该字符串。找到返回TValue,未找到返回nil。
(2) 当查找的key为nil时,直接返回nil。
(3) 当查找的key为数字时,调用luaH_getint,该函数如下

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在(1,sizearray)之间,则直接返回array[key-1]。如果不在这个区间,则计算key的hash值,然后在对应的node*链中去查找。找到返回TValue,未找到返回nil。
(4) 对于其他的类型,都是先计算hash值,然后找到相应的node*位置,然后在该链中去查找。
3 table的赋值 luaH_set函数

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);
}

luaH_set首先查找,如果找到,直接返回key的指针。如果未找到,调用luaH_newkey函数,新建一个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.
** 新建一个KEY,值为nil
*/
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)首先判断key是不是空,为空的话出错
(2)调用mainposition找到主位置mp,主位置即key的hash映射到表中的位置
(3)如果主位置为空,那么直接将key放到主位置。如果主位置不为空,那么先通过getfreepos函数找到一个空闲的位置n(这个位置从末尾开始找)。如果找不到空闲位置,就要调用rehash重新调整hash表,调整好后调用luaH_set设置。如果找到空闲位置,计算mp的主位置othern。如果两个不相等,即mp的主位置不在mp指向的这里,那么mp移到n,并将n放在mp的主位置链上。如果两个相等,即mp的主位置就是这里,将key赋值给n,然后将n放到mp的主位置链上。

这里简单的模拟一下,素组代表hash表,采用取模方式获得hash位置,Last指向最后空余位置。
这里写图片描述
加入14,17。
这里写图片描述
加入27,发现主位置7不为空,判断17的主位置也在7,将27赋给位置0,将17的next指向0。

这里写图片描述
加入30,返现0位置不为空,同时27的主位置不在0,将27移到最后的lastfree,将30赋给0位置。

这里写图片描述
加入15
这里写图片描述
加入25,发现主位置5不为空,15的主位置也在5,将25赋给lastfree,将15next指向位置8。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页