pairs和iparis
- 定义
paris和iparis都用于遍历集合,paris遍历集合中的所有元素,而iparis只遍历数组部分,而且数组部分必须连续。其次,pairs遍历的顺序是不确定的。
- 问题
两个问题,遍历时插入新元素结果为什么不确定,以及同一个数组,为什么用ipairs遍历就是有序的,用pairs遍历就是无序的。
分析
pairs和ipairs的行为只取决于他们的迭代器函数,pairs的迭代器函数是next,而ipairs的迭代器函数是geti,下边我们将对这两个迭代器函数进行分析。
- geti
//idx代表传入迭代器的表
//n代表传入迭代器的数组下标
LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) {
TValue *t;
const TValue *slot;
lua_lock(L);
t = index2value(L, idx); //取出表
if (luaV_fastgeti(L, t, n, slot)) { //获取到t[n]? slot为传出参数,等于t[n]
setobj2s(L, L->top, slot); //设置栈顶为t[n]
}
else {
TValue aux;
setivalue(&aux, n);
luaV_finishget(L, t, &aux, L->top, slot); //否则到元表中找
}
api_incr_top(L); //栈顶加1
lua_unlock(L);
return ttype(s2v(L->top - 1));
}
//luaV_fastgeti宏(是表,如果索引在数组部分,直接返回t[n],否则luaH_getint)
#define luaV_fastgeti(L,t,k,slot) \
(!ttistable(t) \
? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \
: (slot = (l_castS2U(k) - 1u < hvalue(t)->alimit) \
? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \
!isempty(slot))) /* result not empty? */
//表中找key(英文注释很清楚这里就不翻译了)
const TValue *luaH_getint (Table *t, lua_Integer key) {
if (l_castS2U(key) - 1u < t->alimit) /* 'key' in [1, t->alimit]? */
return &t->array[key - 1];
else if (!limitequalsasize(t) && /* key still may be in the array part? */
(l_castS2U(key) == t->alimit + 1 ||
l_castS2U(key) - 1u < luaH_realasize(t))) {
t->alimit = cast_uint(key); /* probably '#t' is here now */
return &t->array[key - 1];
}
else {
Node *n = hashint(t, key); //找到int在hash部分的位置,然后遍历hash桶链表
for (;;) { /* check whether 'key' is somewhere in the chain */
if (keyisinteger(n) && keyival(n) == key)
return gval(n); /* that's it */
else {
int nx = gnext(n);
if (nx == 0) break;
n += nx;
}
}
return &absentkey;
}
}
- next
这个在之前的table分析里面详细说过,这部分就大概看一下。
int luaH_next (lua_State *L, Table *t, StkId key) {
unsigned int asize = luaH_realasize(t); //数组部分大小2的幂
unsigned int i = findindex(L, t, s2v(key), asize); /* find original key */
//遍历数组找到第一个非空槽位
for (; i < asize; i++) { /* try first array part */
if (!isempty(&t->array[i])) { /* a non-empty entry? */
setivalue(s2v(key), i + 1);
setobj2s(L, key + 1, &t->array[i]);
return 1;
}
}
//遍历hash链表
for (i -= asize; cast_int(i) < sizenode(t); i++) { /* hash part */
if (!isempty(gval(gnode(t, i)))) { /* a non-empty entry? */
Node *n = gnode(t, i);
getnodekey(L, s2v(key), n);
setobj2s(L, key + 1, gval(n));
return 1;
}
}
return 0; /* no more elements */
}
结论
在表中,数组和hash部分共存,数字下标连续并不意味着存在数组里,数组空间满首先会填充hash部分,直到hash部分达到阈值或者满才会扩容,所以第一步我们首先要知道,数字下标连续并不会一定放在数组里即可。
然后,ipairs的迭代器是这样的,给出一个数字5,他会找key等于6的元素,先找array[6],然后找hash[6]的桶链表,如果有就返回,没有就结束。所以,ipairs是有序的。而pairs是完全按照数组的顺序和hash链表顺序遍历的,也就是说他先遍历数组,然后从头节点遍历hash部分,所以如果数组部分被hash到hash表里,那就是无序的了。
其次,遍历时插入新元素,如果插入到了hash部分,有可能会插入到当前点后边,可以读出,也可能插入到前边,还有可能发生扩容操作导致resize,最终导致hash表rehash,所以遍历时插入元素的结果时不确定的。