(3) Lua源码系列----table的设计与实现

Lua Table的设计与实现

Lua 版本 5.3.4
最好能够使用gdb

1.数据结构

1.1 key

typedef union TKey {
  struct {
    TValuefields;
    int next;  /* for chaining (offset for next node) */
    /* 这个是offset, 不是指针*/
  } nk;
  TValue tvk;
} TKey;

1.2 hash 节点 Node

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

1.3 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 */
  unsigned int sizearray;  /* size of 'array' array */
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  struct Table *metatable;
  GCObject *gclist;
} Table;

<1> 从上面结构和注释可以看出,Lua的table 分为 数组和哈希两个部分
<2> lsizenode 不是哈希节点的大小,而是log2 of 哈希大小,哈希部分一定是2的整数次幂

2. 初始化

2.1 初始化函数

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

2.2 初始化说明

<1> hash 部分的node, lastfree, lsizenode 是在 setnodevector 初始化,当是空table的时候,node 初始化为所有table 哈希部分为空共享的一个静态变量 dummynode

3. table 插入

无论是数组部分还是哈希部分,赋值的入口都是在 lvm.c: void luaV_execute 中的:

  vmcase(OP_SETTABLE) {
    TValue *rb = RKB(i);
    TValue *rc = RKC(i);
    settableProtected(L, ra, rb, rc);
    vmbreak;
  }

从其中的 宏定义 settableProtected(L, ra, rb, rc) 展开可以看出:
(假设t为table, 键为 k, 值为v)
如果 t[k] 不为nil, 直接赋值; 如果t[k]为nil, 需要添加新的键,然后向对应的值赋值
其中通过 luaH_get 函数知道t[k]是否为空,通过 luaH_newkey 函数添加新的键

3.1 luaH_get 函数

3.1.1 函数代码

const TValue *luaH_get (Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key));
    case LUA_TNUMINT: return luaH_getint(t, ivalue(key));
    case LUA_TNIL: return luaO_nilobject;
    case LUA_TNUMFLT: {
        lua_Integer k;
        if (luaV_tointeger(key, &k, 0)) /* index is int? */
            return luaH_getint(t, k);  /* use specialized version */
        /* else... */
        }  /* FALLTHROUGH */
    default:
        return getgeneric(t, key);
  }
}

我们常用的key类型是 string和int, 着重看这两个部分。

3.1.2 获取key为字符串的值

先根据字符串的hash值,获取其在table中的主节点位置,然后通过主节点的next,逐个比较节点的key 和查找的key,如果相同,返回。

3.1.3 获取key为整数的值

(假设t为table, 键为 key, 值为val)
如果 1<= key && key <= t->sizearray,说明是在数组部分,直接返回数组部分内容即可 return t->array[key-1];
否则 通过key计算哈希值-> 通过哈希值计算在 node 数组中的主位置节点 mainpos -> 如果 mainpos 非空且mainpos已有的 键k和查找的key相等,则返回;否则查找mainpos 的下一个节点,如果不为空且键和key相等,那么就返回该节点。。。 如此循环,直到找到相同的键或者该哈希值所在链的末尾。

3.1.4 实例

(待补充)

3.2 luaH_newkey 函数

3.2.1 函数代码

TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
  Node *mp;
  TValue aux;

  if (ttisnil(key)) luaG_runerror(L, "table index is nil");

  else if (ttisfloat(key)) {

    lua_Integer k;
    if (luaV_tointeger(key, &k, 0)) {  /* does index fit in an integer? */
        setivalue(&aux, k);
        key = &aux;  /* insert it as an integer */
    }
    else if (luai_numisnan(fltvalue(key)))
        luaG_runerror(L, "table index is NaN");
  }
  mp = mainposition(t, key);
  if (!ttisnil(gval(mp)) || isdummy(t)) {  /* main position is taken? */

    Node *othern;
    Node *f = getfreepos(t);  /* get a free place */
    if (f == NULL) {  /* cannot find a free place? */
        rehash(L, t, key);  /* grow table */
        /* whatever called 'newkey' takes care of TM cache */
        return luaH_set(L, t, key);  /* insert key into grown table */
    }
    lua_assert(!isdummy(t));
    othern = mainposition(t, gkey(mp));
    if (othern != mp) {  /* is colliding node out of its main position? */
        /* yes; move colliding node into free position */
        while (othern + gnext(othern) != mp)  /* find previous */
            othern += gnext(othern);
        gnext(othern) = cast_int(f - othern);  /* rechain to point to 'f' */
        *f = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
        if (gnext(mp) != 0) {
            gnext(f) += cast_int(mp - f);  /* correct 'next' */
            gnext(mp) = 0;  /* now 'mp' is free */
    }
        setnilvalue(gval(mp));
    }

    else {  /* colliding node is in its own main position */
        /* new node will go into free position */
    if (gnext(mp) != 0)
        gnext(f) = cast_int((mp + gnext(mp)) - f);  /* chain new position */
    else lua_assert(gnext(f) == 0);

    gnext(mp) = cast_int(f - mp);
     mp = f;
    }
  }

  setnodekey(L, &mp->i_key, key);
  luaC_barrierback(L, t, key);
  lua_assert(ttisnil(gval(mp)));
  return gval(mp);
}

<1> 函数的主流程:开始部分是处理newkey为 浮点数的情况。
mp = mainposition(t, key) :根据当前key值,获取在table中的主位置。和上面 luaH_get 中的获取方式一样。
1. 如果mp所在位置没有被占用,返回mp节点的value值
2. 如果当前位置被占用
2.1 寻找一个 空闲 node, 名称f (getfreepos函数, 利用Table 结构中的 lastfree 成员)
如果f 为空,那么需要调用 rehash,重新调整(扩容)table,然后调用 luaH_set 赋值,return
(rehash 是table 管理空间大小的核心函数,介绍见下面)
2.2 寻找mp节点的key 的主节点位置,命名为 othern
2.2.1 如果 othern != mp ,说明 当前mp节点位置的key 占用了 newkey 的主节点位置,那么下面需要将mp节点的键值对移动到其他位置,然后用newkey 占用,具体过程如下:
a. 根据othern ,逐个next,获得 mp的上一个节点(到2.2.1 分支,说明othern 和mp的哈希值是一样的,链表的头结点是othern)
b. 将mp 值复制到 前面找到的空闲节点 f
c. 如果mp还有下个节点,需要将f 的下个节点修正,将mp的next 置为0.最后将mp的值置为nil(此时mp位置就可以放置新的键值对了)
2.2.2 如果 ohtern == mp, 说明mp节点的key和newkey 主位置一致,要串在统一链表上。后续操作就是插入这个链表。

3.3 rehash 函数

3.3.1 函数代码

猜你喜欢

转载自blog.csdn.net/l101606022/article/details/79039358