1. 一些简单的例子
从例子可以看出,#并不是取得从1开始连续部分数组的长度
print("#{1}:", #{1})
print("#{1,2}:", #{1, 2})
print("#{1,2, 3}:", #{1, 2, 3})
print("#{1,2, 3, 4}:", #{1, 2, 3, 4})
print("#{1, 2, 4}:", #{1, 2, 4})
print("#{1, 3}:", #{1, 3})
输出的结果
#{1}: 1
#{1,2}: 2
#{1,2, 3}: 3
#{1,2, 3, 4}: 4
#{1, 2, 4}: 3
#{1, 3}: 2
// 一个特殊的例子
a = {}
a[1] = 1
a[2] = 2
a[4] = 4
a_len = #a
print(a_len) // 得到 4 并不是2, 也不是3
2.那么#号是在取什么东西呢?
我们跟随下面一个简单的例子看看#在取什么值
a = #{1, 2, 4}
3.在2中解析到的token是
TK_NAME(285) -> = (61) -> # OPR_LEN (35) -> { (123) -> TK_NUMBER(284) -> ,(44) ->TK_NUMBER(284) -> ,(44) ->TK_NUMBER(284) -> ,(44) -> } (125)
{ 的时候会触发一个gafqK_codeABC操作认为是新建一个table
} 只会会触发一个gafqK_codeABC(fs, OP_SETLIST, base, b, c); OP_SETLIST
4.解析完token后在vm中的执行
OP_NEWTABLE
OP_LOADK
OP_LOADK
OP_LOADK
OP_SETLIST
OP_LEN // 来到了我们本章的重点,获取长度
'''
5.OP_LEN做了什么
1.先判断类型是table、string、或者有元方法
2.我们本章是table,所以会执行一下内容
setnvalue(ra, cast_num(gafqH_getn(hvalue(rb))));
6.gafqH_getn做了什么
int gafqH_getn(Table *t)
{
unsigned int j = t->sizearray; // 初始化的时候,创建的数组部分大小是3
// 通过二分查找获取长度 这边会判断j - 1 的地方是否是空的,如果是空的会二分查找,知道不是空
if (j > 0 && ttisnil(&t->array[j - 1]))
{
unsigned int i = 0;
while (j - i > 1)
{
unsigned int m = (i + j) / 2;
if (ttisnil(&t->array[m - 1]))
j = m;
else
i = m;
}
return i;
}
else if (t->node == dummynode) // 这边判断哈希部分存的是不是空的
return j; // 在我们的例子中 {1, 2, 4} 因为取 array[j - 1] 是4然后哈希部分又是空的,所以获得的长度直接认为是3
else
return unbound_search(t, j); // 就算有哈希部分例如{1, 2, 4, a = 1} 因为array[j - 1]不是空的,也会认为长度是3
}
7.通过以上,我们知道lua在获取长度时,用到了二分的方法计算长度
我们开始看一个特殊的例子
a = {}
a[1] = 1
a[2] = 2
a[4] = 4
a_len = #a
print(a_len) // 得到 4 并不是2, 也不是3
在这个例子中,会返回a_len的长度是4, 可是我们只设置了3个值,这是为什么呢?
同样回到gafqH_getn的操作的时候,我们得到了 unsigned int j = t->sizearray; 长度是4,那么就涉及到lua是怎么执行上面三句赋值操作的
8. 与一开始的例子不同, 这次我们多了 OP_SETTABLE操作
TValue *oldval = gafqH_set(L, h, key); // 在 a[1] = 1中 会查找key = 1是否存在我们一开始table是空的显然不存在,那么就会触发newkey操作
9.gafqH_set做了什么
TValue *gafqH_set(gafq_State *L, Table *t, const TValue *key)
{
const TValue *p = gafqH_get(t, key); // a[1] 中 获取 key = 1是否存在
t->flags = 0;
if (p != gafqO_nilobject)
return cast(TValue *, p);
else
{
return newkey(L, t, key); // 因为不存在,所以创建一个新的
}
}
10.newkey做什么
static TValue *newkey(gafq_State *L, Table *t, const TValue *key)
{
Node *n = getfreepos(t); // 获取空余的位置
if (n == NULL)
{
rehash(L, t, key); //插入rehash
return gafqH_set(L, t, key);
}
}
11. rehash做什么
因为没有空余位置,所以需要重新计算hash 给 a[1] 弄出一个位置来
static void rehash(gafq_State *L, Table *t, const TValue *ek)
{
'''
nasize = numusearray(t, nums); // 原本数组部分使用长度
totaluse = nasize;
totaluse += numusehash(t, nums, &nasize); // 原本哈希部分使用长度
nasize += countint(ek, nums); // 给新key 一个位置
totaluse++;
na = computesizes(nums, &nasize); // 需要一个新key nasize 是1
resize(L, t, nasize, totaluse - na);
}
12. computesizes计算新的大小
这里可以看出在计算大小的时候, 每次增长是 * 2的 也就是 twotoi *= 2
那么 当 a[4] 的时候, 需要的narray 是3个, 然后因为增长是 * 2, 最后narray会变成4
static int computesizes(int nums[], int *narray)
{
int i;
int twotoi; /* 2^i */
int a = 0; /* 小于 2^i 的元素个数 */
int na = 0; /* 要进入数组部分的元素数量 */
int n = 0; /* 数组部分的最佳大小 */
for (i = 0, twotoi = 1; twotoi / 2 < *narray; i++, twotoi *= 2)
{
if (nums[i] > 0)
{
a += nums[i];
if (a > twotoi / 2)
{ /* 超过一半的元素存在? */
n = twotoi; /* 最佳尺寸(到现在为止) */
na = a; /* 所有小于 n 的元素将进入数组部分 */
}
}
if (a == *narray)
break; /* 所有元素都已经计算过了 */
}
*narray = n;
gafq_assert(*narray / 2 <= na && na <= *narray);
return na;
}
总结 因为12计算出新的array部分是4个,那么11重新计算resize后就会有array长度部分是4,那么现在10中的gafqH_set就会把a[4]的值插入到数组部分,然后在#取值得时候又因为有array[4-1]出有值,所以认为特殊例子中的长度是4