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

发表评论