Lua 源码学习笔记(3)表

Lua 源码学习笔记(3)表

参考书籍:《Lua设计与实现》

作者书籍对应Github:https://github.com/lichuang/Lua-Source-Internal

  • Lua版本:5.3.5

概述

  • Lua表分为数组和散列表部分,散列表可以存储不能存放在数组部分的数据,唯一的要求是键值不能为nil。
//  lobject.h
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;
  • Common Header :要进行 标记-清除 GC操作的数据类型。
  • lu_byte flags :这是一个byte类型的数据,用于表示这个表中提供了哪些元方法。最开
    始这个flags是空的,也就是0 ,当查找一次之后,如果该表中存在某个元方法,那么将
    该元方法对应的flag bit置为l ,这样下一次查找时只需要比较这个b it 就行了。每个元
    方法对应的bit定义在ltm. h 文件中。
  • lu_byte lsizenode :该表中以2 为底的散列表大小的对数值。同时由此可知,散列表部分
    的大小一定是2 的幕,即如果散列桶数组要扩展的话,也是以每次在原大小基础上乘以2
    的形式扩展。
  • unsigned int sizearray : 数组部分的大小。
  • struct Table *metatable :存放该表的元表。
  • TValue *array :指向数组部分的指针。
  • Node *node :指向该表的散列桶数组起始位置的指针。
  • Node *lastfree : 指向该表散列桶数组的最后位置的指针。
  • GCObject *gclist : GC链表。

是lsizenode使用的是byte类型:由于在散列桶部分,每个散列值相同的数据都会以链表的形式串起来,所以即使数量用完了,也不要紧。因此这里使用byte 类型,而且是原数据以2为底的对数值,因为要根据这个值还原回原来的真实数据,也只是需要移位操作罢了,速度很快。

//lobject.h

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

typedef union TKey {
  struct {
    TValuefields;
    int next;  /* for chaining (offset for next node) */
  } nk;
  TValue tvk;
} TKey;


#define TValuefields	Value value_; int tt_

typedef struct lua_TValue {
  TValuefields;
} TValue;

一般情况下,如果看到一个数据类型是union ,就可以知道这个数据想以一种较为省内存的方式来表示多种用途,而这些用途之间是“互斥”的,也就是说,在某个时刻该数据类型只会是其中的一个含义。


操作算法

查找

  • lobject.h的 findindex 函数

如果 输入的key是一个正整数,并且它的 值> 0 && <=数组大小
​ 尝试在数量且部分查找
否则尝试在散列表部分查找
​ 计算出该key 的散列值.根据此散列值访问Node 数数组得到散列桶所在的位置
​ 遍历该做列树下的所有链农元素,直到找到该key 为止

新增元素

  • lobject.h的 luaH_newkey 函数:根据Key,返回TValue指针。

    /*
    ** 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.
    */

    (1)根据key来查找其所在散列桶的mainposition ,如果返回的结果中,该Node 的值为nil ,那么直接将key 赋值并且返回Node 的TValue指针就可以了。
    (2)再则说明该mainposition 上已经有其他数据了,需要重新分配空间给这个新的key ,然后将这个新的Node 串联到对应的散列桶上。

  • 散列表部分的数据组织是,首先计算数据的key 所在的桶数组位置,这个位置称为
    mainposition 。相同mainposition 的数据以链表形式组织:

在这里插入图片描述

rehash:重新分配表空间
  • lobject.h的rehash函数:
  1. 分配一个位图nums (unsigned int nums[MAXABITS + 1];),将其中的所有位置0 。这个位图的意义在于: nums 数组中第 i 个元素存放的是key在 2(i-1) 和 2i 之间的元素数量。
  2. 遍历Lua表中的数组部分,计算其中的元素数量,更新对应的nums 数组中的元素数量( numusearray 函数)。
  3. 遍历lua表中的散列桶部分,因为其中也可能存放了正整数,需要根据这里的正整数数量更新对应的nums数组元素数量(numusehash 函数)。
  4. 此时nums数组已经有了当前这个Table 中所有正整数的分配统计,逐个遍历nums 数组,获得其范围区间内所包含的整数数量大于50% 的最大索引,作为重新散列之后的数组大小,超过这个范围的正整数,就分配到散列桶部分了( computesizes 函数) 。
  5. 根据上面计算得到的调整后的数组和散列桶大小调整表( resize 函数)。
优化:避免重新散列操作
local a = {}
for i=1,3 do
	a[i] = true
end

-- 最开始, Lua创建了一个空表a 。
-- 在第一次迭代中, a[l]为true 触发了一次重新散列操作, Lua将数组部分的长度设置为2^0 ,即l,散列表部分仍为空。
-- 在第二次迭代中, a[2]为true再次触发了重新散列操作,将数组部分长度设为2^1,即2。
-- 最后一次迭代又触发了一次重新散列操作,将数组部分长度设为2^2 ,即4。

local a = {true, true, true}
-- Lua知道表有3个元素,就直接创建了3个元素的数组。

迭代

  • lobject.h的luaH_next函数:

在数组部分查找数据:
​ 查找成功, 则返回该key 的下一个数据
否则 在散列桶部分查找数据:
​ 查找成功, 则返回该key 的下一个数据
否则
​ 返回0,表示没有元素了

取长度

  • lobject.h的luaH_getn函数:取数组部分长度
/*
** Try to find a boundary in table 't'. A 'boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
lua_Unsigned luaH_getn (Table *t) {
  unsigned int j = t->sizearray;
  if (j > 0 && ttisnil(&t->array[j - 1])) {
    /* there is a boundary in the array part: (binary) search for it */
    unsigned int i = 0;
    while (j - i > 1) {
      unsigned int m = (i+j)/2;
      if (ttisnil(&t->array[m - 1])) j = m;
      else i = m;
    }
    return i;
  }
  /* else must find a boundary in hash part */
  else if (isdummy(t))  /* hash part is empty?  hash部分为空的时候酶制剂返回sizearray*/
    return j;  /* that is easy... */
  else return unbound_search(t, j);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值