本文从两个操作说明一个新的键值插入到table的哈希表中的过程;插入键值对Lua表的操作相当频繁,其函数的性能也关乎到Lua在宿主语言中的性能;一个操作是newkey函数,通过查找一个新的可用的哈希位置来插入键值,当没有可用位置时,引发另外一个操作就是rehash,这个操作会重新判定Lua表(Table)的数组长度和哈希表的长度,从而腾出新的位置插入当前节点;具体过程参见详细分析。
newkey函数分析
参看一下代码注释前请先参考这篇文章
文章主要解释了Lua的Table表中包含两个部分,一部分是数组部分,一部分是哈希表部分,之所以使用这种存储方式,是考虑到Key的格式不仅仅是数字,若是数字则分情况设置到数组或者哈希表部分,其他类型依情况而定,另外一个情况是哈希到哈希列表部分,如果发生碰撞的情况处理,是先使用getfreepos找到空闲位置,然后主位置的节点链接过去,就像一个链表,这也就导致另外一个问题,就是当真正的节点要插入时,可能有其他节点已经占用了这个位置,这个时候的处理就是将这个节点移动到空闲位置上去,将真正的哈希节点设置进来,基本思路便是如此。
1 | static TValue *newkey (lua_State *L, Table *t, const TValue *key) { |
mainposition函数分析
这个函数的主要作用就是将根据key的类型得到哈希值生成索引位置,从Table表中哈希列表所在索引位置得到节点。
hashnum注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 能使用hashnum的情况说明不能设置到Table的数组中了
static Node *hashnum (const Table *t, lua_Number n) {
unsigned int a[numints];
int i;
// 如果是数字0就直接返回0位置的节点(特殊处理)
if (luai_numeq(n, 0)) /* avoid problems with -0 */
return gnode(t, 0);
// lua_Number在这里是double类型,做一次拷贝
memcpy(a, &n, sizeof(a));
// 逐个数字进行累加到a[0]
for (i = 1; i < numints; i++) a[0] += a[i];
// #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1))))
// 将n mod 哈希表长度范围得到的索引,通过gnode得到节点
// 或1与加1的情况不同,加1是末尾为时加1为0,这里或1是保证索引落在2的倍数索引内
// 依次展开sizenode宏
// #define twoto(x) (1<<(x))
// #define sizenode(t) (twoto((t)->lsizenode))
// 以上两句就计算Table的哈希表的长度,lszienode记录的是以2为基的次数
return hashmod(t, a[0]);
}hashstr注释
1
2
3
4
5
6
7// 这里使用String的hash值直接根据哈希列表的长度求模得出索引
// gnode根据索引得到节点
// 这里需要注意的第一地方是使用了&代替求模,这个可以自行科普一下mainposition注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 这里需要注意的是TValue包含了Lua中表示的所有类型
// 这里返回的是Node的指针,也就是哈希列表中的节点地址
static Node *mainposition (const Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TNUMBER:
// 参见上面的注释
// #define nvalue(o) check_exp(ttisnumber(o), (o)->value.n)
return hashnum(t, nvalue(key));
case LUA_TSTRING:
// 参见上面注释
// #define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts)
return hashstr(t, rawtsvalue(key));
case LUA_TBOOLEAN:
return hashboolean(t, bvalue(key));
case LUA_TLIGHTUSERDATA:
return hashpointer(t, pvalue(key));
default:
return hashpointer(t, gcvalue(key));
}
}
newkey的使用
- 我们以luaH_setnum函数为例来看看newkey函数的使用
1 | TValue *luaH_setnum (lua_State *L, Table *t, int key) { |
- luaH_getnum函数得到节点(数组部分或者哈希列表部分)
有人问如何使用Table的数组部分或者哈希部分,主要是通过luaH_getnum函数决定是使用数组部分还是哈希列表部分
1 | const TValue *luaH_getnum (Table *t, int key) { |