Lua5.4源码阅读—表(3)

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 *p = pvalueraw(*kvl);/* 获取对应的指针 */
      return hashpointer(t, p); /* 获取p指针指向的地址值对表t的散列表Node大小的余,对应的节点 */
    }
    case LUA_VLCF: {/* key为c函数类型 */
      lua_CFunction f = fvalueraw(*kvl);/* 获取c函数指针 */
      return hashpointer(t, f);/* 获取f函数指针指向的地址值对表t的散列表Node大小的余,对应的节点 */
    }
    default: {
      GCObject *o = gcvalueraw(*kvl);/* 默认情况获取key作为GC对象 */
      return hashpointer(t, o);/* 获取GC对象o指针指向的地址值对表t的散列表Node大小的余,对应的节点 */
    }
  }
}

/*
** 根据n2不同类型,判断k1和n2是否相等,deadok用来标识是否需要检查n2是否被释放,k1是否为GC对象
*/
static int equalkey (const TValue *k1, const Node *n2, int deadok) {
  if ((rawtt(k1) != keytt(n2)) &&  /* 不是相同类型,包含易变位 */
       !(deadok && keyisdead(n2) && iscollectable(k1))) /* 是否需要检查n2是否被释放,k1是否为GC对象 */
   return 0;  /* cannot be same key */
  switch (keytt(n2)) {
    case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE:
      return 1;
    case LUA_VNUMINT:
      return (ivalue(k1) == keyival(n2));
    case LUA_VNUMFLT:
      return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2)));
    case LUA_VLIGHTUSERDATA:
      return pvalue(k1) == pvalueraw(keyval(n2));
    case LUA_VLCF:
      return fvalue(k1) == fvalueraw(keyval(n2));
    case ctb(LUA_VLNGSTR):
      return luaS_eqlngstr(tsvalue(k1), keystrval(n2));
    default:
      return gcvalue(k1) == gcvalueraw(keyval(n2));
  }
}

散列表部分插入键值对

在表的散列表部分插入键值对,首先查询需要插入的键对应的主位置mp,如果找到的mp不为空,或者表为空,则查找是否存在空闲节点f,不存在则调整表的大小,将该键值对插入调整后的表中,如果存在f,则获取mp节点键对应的主位置othern,如果两个主位置不相同,说明mp在其主位置othern的链表上,从该链表上找点指向mp的前一个节点,将mp节点复制到f节点,将mp节点从othern的链表上剔除,如果两个主位置相同,则将f节点插入到mp和mp指向的下一个节点之间,将mp拷贝到f上,并清空mp,将需要插入的键值对,拷贝到经过上述处理的mp节点。

/*
** 在表的散列表部分插入一个键值对,先找到需要插入的键值对应的主位置mp,如果找到的mp不为空,则查找是存在空闲节点f,不存在则调整表的大小,将该键值对插入调整后的表中
** 如果存在f,则获取mp对应键值的主位置othern,如果两个主位置不相同,说明mp在主位置othern的链表上,找到指向mp的前一个节点,将f节点替换mp节点,将mp节点从othern的链表上剔除,
** 如果两个主位置相同,则将f插入mp和mp指向的下一个节点之间,将mp拷贝到f上,并清空mp,
** 将键值对拷贝到通过上面处理后的mp上
*/
void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) {
  Node *mp;
  TValue aux;
  if (l_unlikely(ttisnil(key)))/* 检查key是否为nil */
    luaG_runerror(L, "table index is nil");
  else if (ttisfloat(key)) {    /* 检查key是否为浮点型 */
    lua_Number f = fltvalue(key);
    lua_Integer k;
    if (luaV_flttointeger(f, &k, F2Ieq)) {  /* 检测key是否为整数值 */
      setivalue(&aux, k);
      key = &aux;  /* 作为一个整数插入 */
    }
    else if (l_unlikely(luai_numisnan(f)))
      luaG_runerror(L, "table index is NaN");
  }
  if (ttisnil(value))/* 检查value是否为nil */
    return;  /* do not insert nil values */
  mp = mainpositionTV(t, key);  /* 查找主位置(即不管冲不冲突散列值的第一个位置),根据key所属的类型,获取key的散列值,并从散列部分取出对应散列值的节点Node */
  if (!isempty(gval(mp)) || isdummy(t)) {  /* 如果找到的节点不为空或者表t的散列部分是空的 */
    Node *othern;
    Node *f = getfreepos(t);  /* 是否存在空闲节点 */
    if (f == NULL) {  /* 不存在 */
      rehash(L, t, key);  /* 调整表大小 */
      /* whatever called 'newkey' takes care of TM cache */
      luaH_set(L, t, key, value);  /* 插入键值对 */
      return;
    }
    lua_assert(!isdummy(t));
    othern = mainposition(t, keytt(mp), &keyval(mp));/* 查找mp的主位置 */
    if (othern != mp) {  /* 两个主位置不是相同位置,即需要插入的键值找到的主位置在othern主位置的链表上 */
      /* yes; move colliding node into free position */
      while (othern + gnext(othern) != mp)  /* 通过othern主位置找到mp的前一个位置 */
        othern += gnext(othern);
      gnext(othern) = cast_int(f - othern);  /* 将mp的前一个位置指向的下一个位置改为f */
      *f = *mp;  /* 复制mp到空闲节点f上 */
      if (gnext(mp) != 0) {
        gnext(f) += cast_int(mp - f);  /* 修改f节点指向的下一个节点为之前mp指向的节点 */
        gnext(mp) = 0;  /* 清空mp */
      }
      setempty(gval(mp));
    }
    else {  /* colliding node is in its own main position */
      /* 将键值对插入到mp(主节点)和mp指向的下一个节点之间即f */
      if (gnext(mp) != 0)
        gnext(f) = cast_int((mp + gnext(mp)) - f);  /* 设置f节点指向的下一个节点为mp指向的下一个节点 */
      else lua_assert(gnext(f) == 0);
      gnext(mp) = cast_int(f - mp); /* 设置mp指向的下一个节点为f */
      mp = f;
    }
  }
  setnodekey(L, mp, key);/* 将key拷贝到mp节点的键上 */
  luaC_barrierback(L, obj2gco(t), key);
  lua_assert(isempty(gval(mp)));
  setobj2t(L, gval(mp), value); /* 将value拷贝到mp节点的值上 */
}

/*
** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i
** 根据需要插入的键值调整表的数组部分和散列表部分的大小,统计总元素数量totaluse,以及为整型的元素数量na以及这些元素在各个2的幂之间的数量,nums[i]表示值在2^(i-1)到2^i之间元素的数量,
** 根据nums和na确定新的数组大小,以使得存入数组部分的元素尽量多并使数组的使用率超过一半,再根据剩余的元素数量确定散列表的大小,散列表的大小为大于剩余元素数量向上取整的2幂
*/
static void rehash (lua_State *L, Table *t, const TValue *ek) {
  unsigned int asize;  /* 优化后的数组部分的大小 */
  unsigned int na;  /* 记录数组部分存的元素个数 */
  unsigned int nums[MAXABITS + 1];
  int i;
  int totaluse; /* 记录表中元素的总数 */
  for (i = 0; i <= MAXABITS; i++) nums[i] = 0;  /* reset counts */
  setlimittosize(t);    /* 设置alimit为表数组部分的实际大小,并设置对应的flags,返回表数组部分的实际大小 */
  na = numusearray(t, nums);  /* 统计数组部分已经使用的元素数量 */
  totaluse = na;  /* all those keys are integer keys */
  totaluse += numusehash(t, nums, &na);  /* 统计散列表部分已经使用的节点数量,并将key为整型的元素记录到nums对应的位置和na */
  /* count extra key */
  if (ttisinteger(ek))/* 需要插入的ek如果是整型,则将其记录到nums对应的位置和na */
    na += countint(ivalue(ek), nums);
  totaluse++;
  /* 计算数组部分新的大小 */
  asize = computesizes(nums, &na);
  /* 根据新的数组大小和散列表部分存入的元素个数调整数组大小和散列表大小 */
  luaH_resize(L, t, asize, totaluse - na);
}

/*
** 返回表t中已经使用的元素个数,并数组nums统计相邻两个2的幂之间使用的元素个数,例如nums[i]为数组部分的第2^(i-1)到第2^i个元素之间已经使用的元素数量
*/
static unsigned int numusearray (const Table *t, unsigned int *nums) {
  int lg;
  unsigned int ttlg;  /* 2^lg */
  unsigned int ause = 0;  /* 数组部分已使用的元素总个数 */
  unsigned int i = 1;  /* count to traverse all array keys */
  unsigned int asize = limitasasize(t);  /* 获取表的数组部分的大小 */
  /* traverse each slice */
  for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) {
    unsigned int lc = 0;  /* 记录两个2的幂直接使用的元素个数 */
    unsigned int lim = ttlg;
    if (lim > asize) {
      lim = asize;  /* adjust upper limit */
      if (i > lim)
        break;  /* no more elements to count */
    }
    /* count elements in range (2^(lg - 1), 2^lg] */
    for (; i <= lim; i++) {
      if (!isempty(&t->array[i-1]))
        lc++;
    }
    nums[lg] += lc;
    ause += lc;
  }
  return ause;
}

/*
** 统计表的散列表部分已经使用的节点的数量,如果对应节点的key为整型,则将其记录到数组nums对应的位置,例如key值为第2^(i-1)到第2^i之间则记录到nums[i],并增加总数*pna
*/
static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) {
  int totaluse = 0;  /* total number of elements */
  int ause = 0;  /* elements added to 'nums' (can go to array part) */
  int i = sizenode(t);
  while (i--) {
    Node *n = &t->node[i];
    if (!isempty(gval(n))) {
      if (keyisinteger(n))
        ause += countint(keyival(n), nums);
      totaluse++;
    }
  }
  *pna += ause;
  return totaluse;
}

/*
**例如key值为第2^(i-1)到第2^i之间则增加nums[i]的计数,返回1标识已计数
*/
static int countint (lua_Integer key, unsigned int *nums) {
  unsigned int k = arrayindex(key);
  if (k != 0) {  /* is 'key' an appropriate array index? */
    nums[luaO_ceillog2(k)]++;  /* count as such */
    return 1;
  }
  else
    return 0;
}

/*
** 根据传入的nums中记录的key为整型可存在数组部分每个2^(i-1)到2^i之间使用的元素个数,和*pna总元素个数,计算数组部分应该分配的大小
** 原理为:查找适合打数组大小,这个大小可以使数组的使用率超过一半
*/
static unsigned int computesizes (unsigned int nums[], unsigned int *pna) {
  int i;
  unsigned int twotoi;  /* 2^i (candidate for optimal size) */
  unsigned int a = 0;  /* number of elements smaller than 2^i */
  unsigned int na = 0;  /* number of elements to go to array part */
  unsigned int optimal = 0;  /* optimal size for array part */
  /* loop while keys can fill more than half of total size */
  for (i = 0, twotoi = 1; twotoi > 0 && *pna > twotoi / 2; i++, twotoi *= 2) {
    a += nums[i];
    if (a > twotoi/2) {  /* more than half elements present? */
      optimal = twotoi;  /* optimal size (till now) */
      na = a;  /* all elements up to 'optimal' will go to array part */
    }
  }
  lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal);
  *pna = na;
  return optimal;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值