最近遇到一件很郁闷的事情,游戏里面我老是当队长!后来发现是因为队伍里每个人的数据会以游戏的ID为key,其余要用到的数据为value,放在一个table里面。然后用pairs遍历这个table,一个个加入队伍,所以谁当队长实际上和pairs的遍历顺序有关,具体来说是和pairs找到的第一个元素有关。一时兴起,重新看了一下lua的table的实现,终于找到原因!
一如既往,先报源码:
Ltable.c table的主要代码在这里
Lobject.h lua的基础对象的定义,包括table
假设我的ID是100005(纯粹是个假设,我当然不可能把我真的ID报出来啦,不过我的ID和这个100005一样苦逼啊有木有)。假设队员的ID是100001,100002,100003,100004,经过试验用pairs来遍历这些key的顺序是100005,100002,100003,100004,100001,下面就看看为什么遍历顺序是这样的。
先看看在Lobject.h中的关键数据结构:
/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
....
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<
lu_byte lsizenode; /* log2 of size of `node' array */
struct Table *metatable;
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
GCObject *gclist;
int sizearray; /* size of `array' array */
} Table;
lua的table实际上是一个数组和hash表的结合体,定义中我们只关心lsizenode,array,node,lastfree和sizearray,其余的部分是每个lua基本类型都有的部分。lsizenode,node,lastfree这三个属性存储的就是哈希表部分;array和sizearray存储的就是数组部分。数组部分比较简单,array就是数组本身,sizearray是数组的大小;哈希表部分的话,node指向哈希表起始地址,lsizenode是log2(node指向的哈希表节点数目),注意2^lsizenode不等于哈希表中存储变量的数目,因为哈希表是有可能有冲突的,所以一个哈希表节点是一个链表的表头,他可能对应多个存储变量。lastfree指向node里面最后一个未用的节点(这个用法很特别,后面会详细说)。
/*
** Tagged Values
*/
#define TValuefields Value value; int tt
....
typedef struct lua_TValue {
TValuefields;
} TValue;
...
typedef union TKey {
struct {
TValuefields;
struct Node *next; /* for chaining */
} nk;
TValue tvk;
} TKey;
typedef struct Node {
TValue i_val;
TKey i_key;
} Node;
Node是哈希表中节点的结构,i_val是这个节点对应value,i_key是节点对应的key,其类型为TKey。TKey的定义很巧究(也很坑爹),这是一个union,TValue的定义实际上也是TValuefields这个宏,所以TKey的内存分布如下所示。
|------------------------------|
|value; |