You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
staticTValue*newkey (lua_State*L, Table*t, constTValue*key) {
Node*mp=mainposition(t, key);//根据key来查找其所在散列桶的mainpositionif (!ttisnil(gval(mp)) ||mp==dummynode) {//如果该mainposition上已经有其他数据了,需要重新分配空间给这个新的key,然后将这个新的Node串联到对应的散列桶上Node*othern;
Node*n=getfreepos(t); /* get a free place */if (n==NULL) { /* cannot find a free place? */rehash(L, t, key); /* grow table */returnluaH_set(L, t, key); /* re-insert key into grown table */
}
lua_assert(n!=dummynode);
othern=mainposition(t, key2tval(mp));
if (othern!=mp) { /* is colliding node out of its main position? *//* yes; move colliding node into free position */while (gnext(othern) !=mp) othern=gnext(othern); /* find previous */gnext(othern) =n; /* redo the chain with `n' in place of `mp' */*n=*mp; /* copy colliding node into free pos. (mp->next also goes) */gnext(mp) =NULL; /* now `mp' is free */setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position *//* new node will go into free position */gnext(n) =gnext(mp); /* chain new position */gnext(mp) =n;
mp=n;
}
}
gkey(mp)->value=key->value; gkey(mp)->tt=key->tt;//如果该Node的值为nil,那么直接将key赋值并且返回Node的TValue指针就可以了luaC_barriert(L, t, key);
lua_assert(ttisnil(gval(mp)));
returngval(mp);
}
staticintcomputesizes (intnums[], int*narray) {
inti;
inttwotoi; /* 2^i */inta=0; /* number of elements smaller than 2^i */intna=0; /* number of elements to go to array part */intn=0; /* optimal size for array part */for (i=0, twotoi=1; twotoi/2<*narray; i++, twotoi *= 2) {
if (nums[i] >0) {
a+=nums[i];
if (a>twotoi/2) { /* more than half elements present? */n=twotoi; /* optimal size (till now) */na=a; /* all elements smaller than n will go to array part */
}
}
if (a==*narray) break; /* all elements already counted */
}
*narray=n;
lua_assert(*narray/2 <= na&&na <= *narray);
returnna;
}
intluaH_getn (Table*t) {
unsigned intj=t->sizearray;
if (j>0&&ttisnil(&t->array[j-1])) {
/* there is a boundary in the array part: (binary) search for it */unsigned inti=0;
while (j-i>1) {
unsigned intm= (i+j)/2;
if (ttisnil(&t->array[m-1])) j=m;
elsei=m;
}
returni;
}
/* else must find a boundary in hash part */elseif (t->node==dummynode) /* hash part is empty? */returnj; /* that is easy... */elsereturnunbound_search(t, j);
}
table
实现了关联数组,即可以同时用数字和字符串索引的数组。table
是一种强大的语言构造。因为table
的泛型特点,简化了使用lua
编写程序所用的数据结构和算法。哈希与数组
直到
Lua 4.0
为止,table
都是作为纯哈希表实现的,所有的键值对都是显式存储的。在Lua 5.0
版本引入了table
的混合表示:每个table
包含了一个哈希部分和一个数组部分,两个部分都可以是空的。Lua
检测一个table
是不是作为一个数组来使用,并自动将数字索引的值移动到数组部分,而非原本的存储在哈希部分。这种分裂只在底层实现层次进行;访问table
域是透明的,即使是对虚拟机来说。table
会自动根据内容使用两个部分。这个混合机制有两个优点。第一,访问整型
key
的操作会变得更快了,因为不再需要哈希。第二,更重要的是,数组部分只占原来哈希部分的一半大小,因为哈希部分需要同时存储key
和value
,而数组部分的key
已经隐含在下标了。结果是,如果一个table
是作为数组使用的,它的表现就像数组一样,只要它的整型key
是密集分布的。而且,哈希部分没有内存或者时间的代价,因为作为数组使用时,哈希部分不存在。反过来说,如果table
是作为记录使用而非数组,那么数组部分就是空的。这些节省下来的内存是重要的。Lua核心突出角色
从
Lua 4.0
开始,全局变量就存储在普通的Lua table
里,称为全局table
。Lua 5.0
用元表和元方法取代了tag
和tag
方法(Lua 3.0
引入的)。元表是普通的Lua table
,元方法是作为元表的域存储的。Lua 5.0
也引入了环境table
,可以附加到Lua
函数上;它们就是Lua
函数索引的全局环境。Lua 5.1
将环境变量table
扩展到C
函数、userdata
和协程,取代了全局的环境变量。这些改动简化了Lua
的实现、Lua
和C
程序员所用的API
,因为全局变量和元方法可以在Lua
里操控,不再需要特殊函数了。实现
先看看表的数据类型定义:
从
Node
类型来看,它包含两个成员,一个是key
,另一个是value(TValue)
。表查找
可以看到,即使是一个正整数的
key
,其存储部分也不见得会一定落在数组部分,这完全取决于它的大小是再落在了当前数组可容纳的空间范围内(OP_NEWTABLE中GETARG_B)。也解释了ipairs
遍历断裂的问题。新增元素
当找不到对应的
key
时,最终都会调用内部的newkey
函数分配一个新的key
来返回。散列表部分的数据组织是,首先计算数据的
key
所在的桶数组位置,这个位置称为mainposition
。相同mainposition
的数据以链表形式组织。整个过程都是在散列桶部分进行的,理由是即使
key
是一个数字,也已经在调用newkey
函数之前进行了查找,结果却没有找到,所以这个key
都会进入散列桶部分来查找。rehash
以上操作涉及重新对表空间进行分配的情况。入口函数是
rehash
,顾名思义,这个函数的作用就是为了做重新散列操作。在重新散列的过程中,除了增大Lua表的大小以容纳新的数据之外,还希望能借此机会对原有的数组和散列桶部分进行调整,让两部分都尽可能发挥其存储的最高容纳效率。那么,这里的标准是什么呢?希望在调整过后,数组在每一个2次方位置容纳的元素数量都超过该范围的50%。 能达到这个目标的话,就认为这个数组范围发挥了最大的效率。
当数字键值的统计跑完之后,得到了这个数组每个元素的数据,也就是得到了落在每个范围内的数据数量。接着会计算怎样才能最大限度地使用这部分空间 。这个算法由函数
computesizes
实现:迭代
在一般算法库的设计中,针对容器类的迭代,会提供一个迭代器的数据,这个数据主要用于维护当前迭代到容器的哪部分数据了,下次再根据这个位置查找下一部分数据。表迭代不是这样设计的,很大的原因是为了兼容数组部分和散列桶部分的访问 。 迭代操作传入的不是一个迭代器,而是
key
。不管是在数组部分还是散列桶部分查找数据,查找成功都会返回该
key
的下一个数据。这个函数一开始就进入
findindex
中进行查询,并区分数组和散列桶部分。findindex
函数的返回结果是一个整数索引,如果这个索引在表的sizearray
之内,则说明落入到数组部分,否则就落入到散列桶部分。在luaH_next
函数中使用这个返回值时,看起来是两个循环,实际上已经根据这个值的范围进行了区分,不会同一个key
走入两个循环中。而在返回散列桶部分时,这个索引值为"sizearray+对应散列桶索引的值”。取长度操作
在
Lua
中,可以使用#
符号对表进行取长度操作。对Lua
中的表进行取长度操作时,如果没有提供该表的元方法_len
,那么该操作只针对该表的序列( sequence )
部分进行。 “序列”指的是表的一个子集{1 ... n}
,其中n
是一个正整数,并且里面每个键对应的数据都不为nil
。如果表中混合了这两种风格的数据,那么优先取数组部分的长度。如果表存在数组部分,在数组部分二分查找返回位置。如果前面的数组部分查不到满足条件的数据,进入散列表部分查找。
所以,尽量不要将一个表混用数组和散列桶部分,即一个表最好只存放一类数据。
Lua
的实现上确实提供了两者统一表示的遍历,但是这不意味着使用者就应该混用这两种方式。从lua-5.1.1中分离出来的table实现代码
ltable
reference
《Lua设计与实现》
Un hermano puede no ser un amigo, pero un amigo será siempre un hermano.
The text was updated successfully, but these errors were encountered: