Lua5.4源码阅读—表

Posted xhjh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua5.4源码阅读—表相关的知识,希望对你有一定的参考价值。

lua中表的实现原理为:按照key的数据类型分成数组部分和散列表部分,数组部分用于存储key值在数组大小范围内的键值对,其余数组部分不能存储的键值对则存储在散列表部分。

表的数据结构

typedef struct Table 
  CommonHeader;
  lu_byte flags;  /* 第8位为0,则表示alimit为数组的实际大小,否则需重新计算 */
  lu_byte lsizenode;  /* 记录Node的数量2的对数值 */
  unsigned int alimit;  /* 记录数组部分的大小 */
  TValue *array;  /* 指向数组部分的首地址 */
  Node *node;   /* 指向node数据块(即散列部分)首地址 */
  Node *lastfree;  /* 记录上一次从node数据块(即散列部分)末尾分配空闲Node的位置 */
  struct Table *metatable;  /* 存放该表的元表 */
  GCObject *gclist;/* GC相关的 */
 Table;


#define BITRAS		(1 << 7)	/* 第8位为1*/
#define isrealasize(t)		(!((t)->flags & BITRAS))/* 根据flags的第8为判断alimit是否为数组部分的实际大小 */
#define setrealasize(t)		((t)->flags &= cast_byte(~BITRAS))/* 设置flags的第8为0 */
#define setnorealasize(t)	((t)->flags |= BITRAS)/* 根据flags的第8为判断alimit是否不为数组部分的实际大小 */

创建表

初始化时,会调用lua_createtable创建指定数组部分大小narray和指定散列表部分大小nrec的表,首先调用luaH_new创建一个空表对象,并将这个对象压入堆栈,再调用luaH_resize对表大小进行调整

LUA_API void lua_createtable (lua_State *L, int narray, int nrec) 
  Table *t;
  lua_lock(L);
  t = luaH_new(L);/* 创建一个空表对象 */
  sethvalue2s(L, L->top, t);    /* 将表对象添加到堆栈上 */
  api_incr_top(L);/* 增加堆栈L->top */
  if (narray > 0 || nrec > 0)
    luaH_resize(L, t, narray, nrec);
  luaC_checkGC(L);
  lua_unlock(L);


/*
** 创建一个空表对象
*/
Table *luaH_new (lua_State *L) 
  GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table));  /* 创建一个Table类型的GC对象 */
  Table *t = gco2t(o);
  t->metatable = NULL;
  t->flags = cast_byte(maskflags);  /* table has no metamethod fields */
  t->array = NULL;
  t->alimit = 0;
  setnodevector(L, t, 0);
  return t;

调整表大小

调整表大小分为两部分,数组部分大小和散列表部分大小,首先调整的是数组部分的大小,根据传入的新数组部分的大小,分为扩大和缩小,扩大不需要进行其他处理,分配一块新大小的数组将老的数据拷贝到新的数据块中,缩小数组则需要先根据新的散列表大小,分配一块新的散列表数据,将缩小部分的数组元素插入到新散列表中,并将老散列表中的元素插入新散列表中

/*
** 根据给定的新的大小调整表,newasize为表t新的数组部分的大小,nhsize为散列表部分
** 如果是缩小数组部分,缩小部分的元素会先记录到,新的大小的散列表中,然后将旧表散列表部分的元素重新插入表中
*/
void luaH_resize (lua_State *L, Table *t, unsigned int newasize,
                                          unsigned int nhsize) 
  unsigned int i;
  Table newt;  /* to keep the new hash part */
  unsigned int oldasize = setlimittosize(t); /* 设置alimit为表数组部分的实际大小,并设置对应的flags,返回表数组部分的实际大小 */
  TValue *newarray;
  /* 根据指定nhsize值创建适合大小的newt的Node表的大小 */
  setnodevector(L, &newt, nhsize);
  if (newasize < oldasize)   /* 缩小表的数组部分 */
    t->alimit = newasize;  /* 先设置数组的大小为新的大小等下数组缩小部分的元素重新插入会用到 */
    exchangehashpart(t, &newt);  /* 交互老表t和新表newt的散列部分 */
    /* 将缩小部分的元素重新插入到新的散列表 */
    for (i = newasize; i < oldasize; i++) 
      if (!isempty(&t->array[i]))/* 缩小部分的元素不为nil类型 */
        luaH_setint(L, t, i + 1, &t->array[i]);/* 以数组索引加1为key将对应元素插入到散列表中 */
    
    t->alimit = oldasize;  /* 将数组的大小设置回原来的大小 */
    exchangehashpart(t, &newt);  /* 交互老表t和新表newt的散列部分,即上面缩小部分的数组元素记录到表newt中的散列表中 */
  
  /* 分配一个新的大小的数组部分的内存块,newasize>oldasize则扩展,newasize<oldasize为缩小 */
  newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue);
  if (l_unlikely(newarray == NULL && newasize > 0))   /* 分配失败 */
    freehash(L, &newt);  /* 释放已经分配的散列表部分的内存 */
    luaM_error(L);  /* raise error (with array unchanged) */
  
  /* allocation ok; initialize new part of the array */
  exchangehashpart(t, &newt);  /* 交互老表t和新表newt的散列部分,交互后表t中只会包含如果是缩小数组部分的元素,newt中包含了原来表t中的散列表部分的元素 */
  t->array = newarray;  /* 设置新数组部分 */
  t->alimit = newasize; /* 设置新数组部分的大小 */
  for (i = oldasize; i < newasize; i++)  /* 如果是扩展数组部分,则清空扩展的数组部分的元素 */
     setempty(&t->array[i]);    /* 设置对应元素为空类型 */
  /* re-insert elements from old hash part into new parts */
  reinsert(L, &newt, t);  /* newt中包含了原来表t中的散列表部分的元素,将这一部分元素重新插入表t中 */
  freehash(L, &newt);  /* 释放旧的散列表部分的元素 */


/*
** 设置alimit为表数组部分的实际大小,并设置对应的flags,返回表数组部分的实际大小
*/
static unsigned int setlimittosize (Table *t) 
  t->alimit = luaH_realasize(t);
  setrealasize(t);
  return t->alimit;


/*
** 给表t创建一个大小最接近size向上取整的2幂值个数的Node类型数据块(即散列表部分大小),
** 即n*sizeof(Node)大小的数据块,n为最接近size的向上取整的2的幂值,
** 这块数据的大小不能超过整型的最大值,初始化每个Node元素
*/
static void setnodevector (lua_State *L, Table *t, unsigned int size) 
  if (size == 0)   /* 表的散列表部分没有元素 */
    t->node = cast(Node *, dummynode);  /* use common 'dummynode' */
    t->lsizenode = 0;
    t->lastfree = NULL;  /* signal that it is using dummy node */
  
  else 
    int i;
    int lsize = luaO_ceillog2(size);/* 计算log2(size)向上取整的值 */
    if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE)/* 检查有没有超出范围 */
      luaG_runerror(L, "table overflow");
    size = twoto(lsize);/* 2^lsize */
    t->node = luaM_newvector(L, size, Node);/* 分配n个t类型的内存 */
    for (i = 0; i < (int)size; i++) 
      Node *n = gnode(t, i);/* 获取对应的节点 */
      gnext(n) = 0;
      setnilkey(n); /* 设置key的类型为nil类型 */
      setempty(gval(n));
    
    t->lsizenode = cast_byte(lsize);/* 记录Node的数量2的对数值 */
    t->lastfree = gnode(t, size);  /* 指向node数据块结尾的下一个Node */
  


/*
** 交换t1和t2的散列部分
*/
static void exchangehashpart (Table *t1, Table *t2) 
  lu_byte lsizenode = t1->lsizenode;
  Node *node = t1->node;
  Node *lastfree = t1->lastfree;
  t1->lsizenode = t2->lsizenode;
  t1->node = t2->node;
  t1->lastfree = t2->lastfree;
  t2->lsizenode = lsizenode;
  t2->node = node;
  t2->lastfree = lastfree;


/*
** 将表ot中的所有散列表中的元素插入到表t中
*/
static void reinsert (lua_State *L, Table *ot, Table *t) 
  int j;
  int size = sizenode(ot);  /* 获取表ot中散列表部分的大小 */
  for (j = 0; j < size; j++)   /* 遍历表ot中散列表部分的每个元素 */
    Node *old = gnode(ot, j);
    if (!isempty(gval(old)))   /* 该元素值不为空 */
      /* doesn't need barrier/invalidate cache, as entry was
         already present in the table */
      TValue k;
      getnodekey(L, &k, old);   /* 获取该元素的key */
      luaH_set(L, t, &k, gval(old));
    
  

插入键值对

以luaH_set为例,首先通过luaH_get从表中查找对应key的键值对是否存在,存在则返回对应的值对象,luaH_get根据key的类型,对应短字符串类型,根据短字符串的散列值从散列表部分查找,对应整型,小数部分为0的浮点型,则先从数组部分查找,如果数组部分不存在则从散列表部分查找,其他走默认,从表的散列表部分查找,如果不存在,则调用luaH_newkey插入键值对到散列表部分,存在则将值设置到返回的对象上。

/*
** 插入键值对
*/
void luaH_set (lua_State *L, Table *t, const TValue *key, TValue *value) 
  const TValue *slot = luaH_get(t, key);    /* 通过键值key获取其在表t中对应的值 */
  luaH_finishset(L, t, key, slot, value);   /* 如果slot在表中存在,则将值value赋值给表中key对应的值,否则在散列表部分插入键值对 */


/*
** 通过键值key获取其在表t中对应的值
*/
const TValue *luaH_get (Table *t, const TValue *key) 
  switch (ttypetag(key)) /* 获取key的类型包含4-5易变位的 */
    case LUA_VSHRSTR: /* key为短字符串类型 */
        return luaH_getshortstr(t, tsvalue(key));
    case LUA_VNUMINT: /* key为整数类型 */
        return luaH_getint(t, ivalue(key));/* 先从数组部分查找,再从散列表部分查找 */
    case LUA_VNIL: /* key为nil */
        return &absentkey;
    case LUA_VNUMFLT: /* key为浮点数类型 */
      lua_Integer k;
      if (luaV_flttointeger(fltvalue(key), &k, F2Ieq)) /* 小数部分为0的浮点数 */
        return luaH_getint(t, k);  /* 先从数组部分查找,再从散列表部分查找  */
      /* else... */
      /* FALLTHROUGH */
    default:
      return getgeneric(t, key, 0); /* 从表t的散列表部分查找键为key的值是否存在,存在则返回 */
  


/*
** 从表t中查找短字符串为键的值
*/
const TValue *luaH_getshortstr (Table *t, TString *key) 
  Node *n = hashstr(t, key);/* 根据短字符串的散列值对散列表大小取余去获取对应的节点 */
  lua_assert(key->tt == LUA_VSHRSTR);
  for (;;)   /* check whether 'key' is somewhere in the chain */
    if (keyisshrstr(n) && eqshrstr(keystrval(n), key))/* 判断是否为相同字符串 */
      return gval(n);  /* that's it */
    else 
      int nx = gnext(n);
      if (nx == 0)
        return &absentkey;  /* not found */
      n += nx;
    
  


/*
** 根据给的key先从表t的数组部分查找,是否存在对应的元素,如果数组部分不存在,则从散列表部分查找,
** 散列值得计算是根据key对表的散列表大小取余,冲突的解决法方是,根据散列值找到对应的Node,如果当前Node的键值不为key,则根据当前Node的next偏移到下一个Node进行比较
** 找到对应的key,则返回对应值TValue的指针,未找到则返回缺失键值的指针
*/
const TValue *luaH_getint (Table *t, lua_Integer key) 
  if (l_castS2U(key) - 1u < t->alimit)  /* key是否在数组大小alimit内 */
    return &t->array[key - 1];  /* 返回对应数组的元素 */
  else if (!limitequalsasize(t) &&  /* alimit是否为表的数组部分的实际大小 */
           (l_castS2U(key) == t->alimit + 1 ||
            l_castS2U(key) - 1u < luaH_realasize(t))) 
    t->alimit = cast_uint(key);  /* 设置alimit为key */
    return &t->array[key - 1];   /* 返回对应数组的元素 */
  
  else 
    Node *n = hashint(t, key);/* 获取key对应的散列表中的Node节点的指针 */
    for (;;)   /* check whether 'key' is somewhere in the chain */
      if (keyisinteger(n) && keyival(n) == key) /* 如果获取到的n的key为整型,且key的值等于传入的key值 */
        return gval(n);  /* 返回n的值的指针 */
      else 
        int nx = gnext(n);/* 获取下一个的偏移量(冲突的解决方法) */
        if (nx == 0) break;
        n += nx;
      
    
    return &absentkey;  /* 不存在对应的key */
  


/*
** 从表t的散列表部分查找键为key的值是否存在,存在则返回,deadok是否检查搜到的点是否被释放
*/
static const TValue *getgeneric (Table *t, const TValue *key, int deadok) 
  Node *n = mainpositionTV(t, key); /* 根据key所属的类型,获取key的散列值,并从散列部分取出对应散列值的节点Node */
  for (;;)   /* check whether 'key' is somewhere in the chain */
    if (equalkey(key, n, deadok))   /* 检查n的key值和传入的key是否相等 */
      return gval(n);  /* 散列表中存在键为key的值 */
    else /* 散列值冲突的解决 */
      int nx = gnext(n);
      if (nx == 0)
        return &absentkey;  /* not found */
      n += nx;
    
  


/*
** 如果slot在表中存在,则将值value赋值给表中key对应的值,否则在散列表部分插入键值对
*/
void luaH_finishset (lua_State *L, Table *t, const TValue *key,
                                   const TValue *slot, TValue *value) 
  if (isabstkey(slot))/* 是否为缺少类型 */
    luaH_newkey(L, t, key, value);/* 插入一个键值对到散列表部分 */
  else
    setobj2t(L, cast(TValue *, slot), value);/* 将value的内容拷贝到对应的slot上 */

散列表部分查找

从散列表部分查找指定的key,首先会根据key的类型,计算对应的散列值,再根据散列值获取到其对应在散列表中的节点(即该散列值的主位置),冲突解决沿着主位置的链表依次变量各个节点是否存在和需要查的key相等的节点,如果存在则返回,不存在则返回缺失类型

/*
** 从表t的散列表部分查找键为key的值是否存在,存在则返回,deadok是否检查搜到的点是否被释放
*/
static const TValue *getgeneric (Table *t, const TValue *key, int deadok) 
  Node *n = mainpositionTV(t, key); /* 根据key所属的类型,获取key的散列值,并从散列部分取出对应散列值的节点Node */
  for (;;)   /* check whether 'key' is somewhere in the chain */
    if (equalkey(key, n, deadok))   /* 检查n的key值和传入的key是否相等 */
      return gval(n);  /* 散列表中存在键为key的值 */
    else /* 散列值冲突的解决 */
      int nx = gnext(n);
      if (nx == 0)
        return &absentkey;  /* not found */
      n += nx;
    
  


/*
** 查找主位置(即不管冲不冲突散列值的第一个位置),根据key所属的类型,获取key的散列值,并从散列部分取出对应散列值的节点Node
*/
static Node *mainpositionTV (const Table *t, const TValue *key) 
  return mainposition(t, rawtt(key), valraw(key));


/*
** 查找主位置(即不管冲不冲突散列值的第一个位置),根据ktt所属的类型,获取kvl的散列值,并从散列部分取出对应散列值的节点Node
*/
static Node *mainposition (const Table *t, int ktt, const Value *kvl) 
  switch (withvariant(ktt)) 
    case LUA_VNUMINT:      /* key为整数类型 */
      lua_Integer key = ivalueraw(*kvl);
      return hashint(t, key);   /* 返回对应整数值对散列表大小取余的key对应的节点 */
    
    case LUA_VNUMFLT:      /* key为浮点数类型 */
      lua_Number n = fltvalueraw(*kvl);
      return hashmod(t, l_hashfloat(n));
    
    case LUA_VSHRSTR:      /* key为短字符串类型 */
      TString *ts = tsvalueraw(*kvl);
      return hashstr(t, ts);
    
    case LUA_VLNGSTR:      /* key为长字符串类型 */
      TString *ts = tsvalueraw(*kvl);
      return hashpow2(t, luaS_hashlongstr(ts));
    
    case LUA_VFALSE:     /* key为bool值false */
      return hashboolean(t, 0);
    case LUA_VTRUE:     /* key为bool值true */
      return hashboolean(t, 1);
    case LUA_VLIGHTUSERDATA: /* 指针类型(不需要GC) */
      void 以上是关于Lua5.4源码阅读—表的主要内容,如果未能解决你的问题,请参考以下文章

Lua5.4源码阅读—字符串

特定场景下SQL的优化

使用sql语句复制一张表

mysql 拆分数据库 现在我的Mysql下面的一个库内有很多表,我想把老表迁移走放在另外一个库下面,如何完成

TcaplusDB知识库TcaplusDB 表管理中如何重建表?

mysql 常用命令