lua table的长度问题


细谈一下lua里很多人有疑问的table长度问题。

1.	> tbl = {1,2,3}  
	> print(#tbl)
	3
	> 
2.	> tbl = {1,nil,3}
	> print(#tbl)    
	3
	> 
3.	> tbl = {1,nil,3,nil}
	> print(#tbl)      
	1
	> 


 
  

情况1正常,情况2有点不正常,情况3很不正常,好,先上源码。

int luaH_getn (Table *t) {
  unsigned int j = t->sizearray;
  if (j > 0 && ttisnil(&t->array[j - 1])) {
    /* there is a boundary in the array part: (binary) search for it */
    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 must find a boundary in hash part */
  else if (isdummy(t->node))  /* hash part is empty? */
    return j;  /* that is easy... */
  else return unbound_search(t, j);
}

此函数是用来求知table的长度,理解这个函数对于lua table的整个原理有很大帮助。

条件1:j>0而且数组部分最后一个为空,则进入下面的二分查找,此二分查找就是找到一个i不为空,j为空的的两个索引,当j-i>1就跳出来返回i.

条件2:j<=0或者数组最后一个不为空,而且没有hash部分,此时,没办法,没得统计,折中返回数组的长度的。

条件3:j<=0或者数组最后一个不为空,有hash部分,进入到unbound_search,从字面意思,就是在一个乱的区间查找,此函数最终也是一个二分查找。

先理解条件1,最上面的情况三就符合条件1的情形,好,直接上gdb.下面的截图,慢慢看。lua table的长度问题_第1张图片

数组长度j = t->t->sizearray = 4,j>0,把最后一个元素打印出来:


tt_ = 0表示此元素为nil值,符合条件1,进入到二分查找,此二分查找就是找到一个左边不为nil,右边为nil时的左边索引。

i = 0,j = 4,m = (i+j)/2 = 2,m-1 = 1,索引为1的元索是nil,0才是1,所以j = m = 2;

i = 0,j = 2,m = (i+j)/2 = 1,m-1 = 0,索引为0的元索是1,所以i = m = 1;

i = 1,j=2,退出循环,长度为1.

 

可以得知,就算仅仅统计只有数组部分的长度也有可能不正常。

条件2制造很简单,跟情况2符合,看gdb:

lua table的长度问题_第2张图片

tbl = {1,nil,3},此table度度j = t->sizearray = 3,最后一个元素tt_ = 3,n = 3,就表示此value为number类型,值为3,也就是tbl最后一个元素不为nil,t->node 而且isdummy,hash部分也为空,折中直接返回数组长度3.

先看unbound_search函数实现:

static int unbound_search (Table *t, unsigned int j) {
  unsigned int i = j;  /* i is zero or a present index */
  j++;
  /* find `i' and `j' such that i is present and j is not */
  while (!ttisnil(luaH_getint(t, j))) {
    i = j;
    j *= 2;
    if (j > cast(unsigned int, MAX_INT)) {  /* overflow? */
      /* table was built with bad purposes: resort to linear search */
      i = 1;
      while (!ttisnil(luaH_getint(t, i))) i++;
      return i - 1;
    }
  }
  /* now do a binary search between them */
  while (j - i > 1) {
    unsigned int m = (i+j)/2;
    if (ttisnil(luaH_getint(t, m))) j = m;
    else i = m;
  }
  return i;
}

const TValue *luaH_getint (Table *t, int key) {
  /* (1 <= key && key <= t->sizearray) */
  if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray))
    return &t->array[key-1];
  else {
    lua_Number nk = cast_num(key);
    Node *n = hashnum(t, nk);
    do {  /* check whether `key' is somewhere in the chain */
      if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
        return gval(n);  /* that's it */
      else n = gnext(n);
    } while (n);
    return luaO_nilobject;
  }
}

此函数传入一个数组长度,也就是认为数组满了,才进入到此函数。

第一个while就是一直查找到一个j,而且以此j为key,不存在于table,包括数组部分和hash部分(luaH_getint的实现),侧退出第一个while,如果中途j>MAX_INT,没办法,此j已经好大了,只能resort to linear search了,就是转到线性查找,一个一个找到一个为空就返回I-1为table的长度。

 在第一个while找到j之后,此时i和j就是左边不为nil,右边为nil的两个值,在此两值之前再二分查找,查找到一个最小的左边不为nil,右边为nil的两个值,返回前者。

 好,制造此情况,看gdb:

lua table的长度问题_第3张图片

tbl = {1,nil,3,qs = 19891103},数组长度为3,最后一个元素不为nil,

lua table的长度问题_第4张图片

有hash部分,见qs = 19891103,进入到一个杂乱无章的区间内查找(unbound_search),在进入第一个while时i=3,j=4,luaH_getint的时候

,以key为4在数组部分和hash部分都不存在的。直接进入到以左边为3,右边为4的二分查找(此二分查找上面有说)。直接就返回3为table的长度。

可见,进入到unbound_search的时候,此时获得数据长度,有可能靠谱,大部分情况都是不靠谱的。

如果数组部分是满的而且没有空洞情况,而且存在于hash部分的key跟数组部分是连续起来的,此时获取长度是达到预期的,有hash部分的时候就要进入到unbound_search查找,假如tbl = {1,2,3,[4] = 4},此时,对外来看,此tbl确实是一个数组,但在内存里,并不是,1,2,3在数组部分,4在hash部分,所以在进入unbound_search,此时获取长度也是正常的.



你可能感兴趣的:(lua)