lua table 源码分析

#lua table 源码分析
lua使用table的单一结构,既可以做array,又可以成为hash,链表,树等结构,是一种简洁高效的使用形式。即使是对虚拟机来说,访问表项也是由底层自动统一操作的,因而用户不必考虑这种区别。表会根据其自身的内容自动动态地使用这两个部分:数组部分试图保存所有那些键介于1 和某个上限n之间的值。非整数键和超过数组范围n 的整数键对应的值将被存入散列表部分。

首先看下table的数据结构定义

(lobject.h)
319 /*
320 ** Tables
321 */
322 
323 typedef union TKey {
324   struct {
325     TValuefields;
326     struct Node *next;  /* for chaining */
327   } nk;
328   TValue tvk;
329 } TKey;
330 
331 
332 typedef struct Node {
333   TValue i_val;
334   TKey i_key;
335 } Node;
336 
337 
338 typedef struct Table {
339   CommonHeader;
340   lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
341   lu_byte lsizenode;  /* log2 of size of `node' array */
342   struct Table *metatable;
343   TValue *array;  /* array part */
344   Node *node;
345   Node *lastfree;  /* any free position is before this position */
346   GCObject *gclist;
347   int sizearray;  /* size of `array' array */
348 } Table;

array: lua表的数组部分起始位置的指针。
node:lua表hash数组的起始位置的指针。
sizearray:lua表数组部分的指针。

根据数据结构看:table的数组部分和hash部分是分别存储的。那么问题来了,什么样的数据会进去数组部分,什么样的数据会进入hash部分?

[0]是一定进入hash,[string]一定是hash的,[int]可能是array,也可能是hash。

是array还是hash的算法是:

数组在每一个2次方位置,其容纳的元素数量都超过了该范围的50%,能达到这个目标的话,那么Lua认为这个数组范围就发挥了最大的效率。

举个例子:

local p = {[0]=1,[1]=2,[2]=2,[4]=3}
print(#p)  
local px = {[0]=1,[1]=2,[2]=2,[5]=3}
print(#px)

输出: 4
输出: 2

算法简说:
除去0,index 1, 2,4 一共3个元素。
算法 : 2^i < 3,i = 2, max_idx = 2^2 =4, index大于4的进入hash。

算法详解:

(ltable.c)
189 static int computesizes (int nums[], int *narray) {
190   int i;
191   int twotoi;  /* 2^i */
192   int a = 0;  /* number of elements smaller than 2^i */
193   int na = 0;  /* number of elements to go to array part */
194   int n = 0;  /* optimal size for array part */
195   for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) {
196     if (nums[i] > 0) {
197       a += nums[i];
198       if (a > twotoi/2) {  /* more than half elements present? */
199         n = twotoi;  /* optimal size (till now) */
200         na = a;  /* all elements smaller than n will go to array part */
201       }
202     }
203     if (a == *narray) break;  /* all elements already counted */
204   }
205   *narray = n;
206   lua_assert(*narray/2 <= na && na <= *narray);
207   return na;
208 }

nums[]: 构建的一个数组,根据index的大小,添加计数对应的位置,保证 2^(i-1) < index < 2^i。
narray:数组的size


另外: 对于数组的定义

local px = {[0]=1,[1]=2,[2]=2,[5]=3}  --hash
local px = {1,2,2,3}  --array

当对于数组关键节(i = 2^n)点进行定义,修改的时候才会触发computesizes,重新进行分配。

新的数组部分的大小是满足以下条件:

  • 1到n 之间至少一半的空间会被利用(避免像稀疏数组一样浪费空间);
  • 并且n/2+1到n 之间的空间至少有一个空间被利用(避免n/2 个空间就能容纳所有数据时申请n 个空间而造成浪费)。

当新的大小计算出来后,Lua 为数组部分重新申请空间,并将原来的数据存入新的空间。


这种混合型结构有两个优点。

  1. 存取整数键的值很快,因为无需计算散列值。
  2. 第二,也是更重要的,相比于将其数据存入散列表部分,数组部分大概只占用一半的空间,因为在数组部分,键是隐含的,而在散列表部分则不是。

结论就是,如果表被当作数组用,只要其整数键是紧凑的(非稀疏的),那么它就具有数组的性能,而且无需承担散列表部分的时间和空间开销,因为这种情况下散列表部分根本就不存在。相反,如果表被当作关联数组用,而不是当数组用,那么数组部分就可能不存在。这种内存空间的节省很重要,因为在Lua 程序中,常常创建许多小的表。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值