Lua之table长度求解

取长度使用到的函数

    /*
    ** Try to find a boundary in table 't'. A 'boundary' is an integer index
    ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
    */
    lua_Unsigned 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))  /* hash part is empty? */ // 数组最后一个元素不为空,但是 hash 部分为空
        return j;  /* that is easy... */
      else return unbound_search(t, j); // 数组最后一个元素不为空,hash 部分也不为空
    }
/*
    首先判断的是,数组最后一个元素是否为空
        为空
            二分法查找
        不为空,但 hash 部分为空
            返回数组长度作为要获取的长度
        不为空,hash 也不为空
            unbound_search(t, j),实现如下
*/
    static lua_Unsigned unbound_search (Table *t, lua_Unsigned j) {
      lua_Unsigned 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))) { // 找到 t[j] 不为空, t[2*j] 为空,那么长度在 j 与 2*j 之间
        i = j;
        if (j > l_castS2U(LUA_MAXINTEGER) / 2) {  /* overflow? */
          /* table was built with bad purposes: resort to linear search */
          i = 1;
          while (!ttisnil(luaH_getint(t, i))) i++;
          return i - 1;
        }
        j *= 2;
      }
      /* now do a binary search between them */
      while (j - i > 1) { 
        lua_Unsigned m = (i+j)/2; // 二分法查找
        if (ttisnil(luaH_getint(t, m))) j = m;
        else i = m;
      }
      return i;
    }

数组的长度为 j,hash 部分从 j+1 开始遍历,j 每次扩大两倍,找到t[j] 不为空, t[2*j] 为空,然后通过二分法查找,找到最终的值。

实例

例1 (全部为数组部分)

    local t = {1, 2, 3, 4}
    print(#t)   -- 4

解释:t 没有 hash 部分,数组长度为4。t[4] 不为空,所以返回 j = 4

例2

    local t = {1, nil, 3}
    print(#t)   -- 3

解释:t没有 hash 部分,数组长度为3。t[3] 不为空,所以返回 j = 3

例3

    local t = {1, nil, 3, nil}
    print(#t)   -- 1

解释:t 没有 hash 部分,数组长度为4。t[4] 为空,所以利用二分法查找

    i = 0, j = 4, m = (i+j)/2 = 2, array[2] 为空, j = m = 2;
    i = 0, j = 2, m = (i+j)/2 = 1, array[1] 不为空, i = m = 1;
    i = 1, j = 2, 条件 j - i > 1 不满足,循环终止,返回 i = 1。

例4(全部为 hash 部分)

    local t = {[1] = 1, [3] = 3, [5] = 5, [6] = 6, [7] = 7}     
    print(#t)   -- 1

解释:t没有数组元素。调用 unbound_search 函数,hash 部分从 j = 1 开始遍历,其中 i记录的是上一个 j的值

    i = 0, j = 1, t[1] 不为空, i = j = 1, j = 2
    i = 1, j = 2, t[1] 为空, j - i = 1 > 1 不成立,返回 i = 1。

例5

    local t = {[1] = 1, [2] = 2, [4] = 4, [6] = 6}  
    print(#t)   -- 6

解释:t没有数组元素,调用 unbound_search 函数,hash 部分从 j = 1 开始遍历

    i = 0, j = 1, t[1] 不为空, i = j = 1, j = 2
    i = 1, j = 2, t[2] 不为空, i = j = 2, j = 4
    i = 2, j = 4, t[4] 不为空, i = j = 4, j = 8
    i = 4, j = 8, t[8] 为空, j - i = 4 > 1 成立,通过二分法查找值

    i = 4, j = 8, m = (i+j)/2 = 6, t[6] 不为 nil, i = m = 6;
    i = 6, j = 8, m = (i+j)/2 = 7, t[7] 为 nil, j = m = 7;
    i = 6, j = 7, 条件 j - i > 1 不满足,循环终止,返回 i = 6。

例6(既包含数组部分,包含hash 部分)

    local t = {1, 2, 3, [5] = 5}
    print(#t)   -- 3

解释:数组部分长度为3,hash 部分长度为1。由于 t[3] 不为空,同时 hash 部分不为空,所以调用 unbound_search 函数,hash 部分从 j = 4 开始遍历

    i = 3, j = 4, t[4] 为空, j - i = 1 > 1 不成立,返回 i = 3

例7

    local t = {1, 2, 3, [4] = 4}
    print(#t)   -- 4

解释:数组部分长度为3,hash 部分长度为1。由于 t[3] 不为空,同时 hash 部分不为空,所以调用 unbound_search 函数,hash 部分从 j = 4 开始遍历

    i = 3, j = 4, t[4] 不为空, i = j = 4, j = 8
    i = 4, j = 8, t[8] 为空, j - i = 4 > 1 成立
    通过二分法查找,最终返回 i = 4

例8

   local t = {1, 2, 3, nil, [5] = 5}
   print(#t)   -- 3

解释:数组部分长度为4,hash 部分长度为1。由于 t[4] 为空,则在数组部分利用二分法查找,参考例3,最终返回 i = 3

以上都是在创建 table 时确定好了数组部分和 hash 部分,但是如果新增键值的话,可能会造成调用 rehash 函数,重新分配数组和 hash 部分。

例9

    local t = {1, [4] = 4}
    print(#t)   -- 1
    t[3] = 3    -- 添加键值
    print(#t)   -- 4

第一个为1,可以参考上面的例子,当添加键值的时候,重新分配,结果是数组部分长度为4,hash 部分为0,等价于

 local t = {1, nil, 3, 4}

参考Lua之table新增整数键值。由于数组部分 t[4]不为空,返回 i = 4

例10

    local t = {1, [5] = 5}
    t[3] = 3
    print(#t)   -- 1

当添加键值的时候,重新分配,结果是数组部分长度为1,hash 部分为2,等价于

 local t = {1, [3] = 3, [5] = 5}

例11

    local t = {1, 2,  [5] = 5}
    t[4] = 4
    print(#t) -- 5
等价于
    local t = {1, 2, nil, 4, [5] = 5}

总结
(1)尽量不要在一个表中混用数组或散列桶部分,即一个表最好只存放一类数据。lua的实现上确实提供了两者统一表示的遍历,但是这并不意味着使用者就应该混用这两种方式。
(2)尽量不要在表中存放nil值,这会让取长度操作的行为不稳定。
(3)尽量避免重新散列操作,因为这个操作的代价极大,通过预分配、只使用数组部分等策略规避这个lua解释器背后的动作,能提升不少效率。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容

  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,658评论 24 1,002
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,608评论 18 399
  • 荼蘼是个有着蜜意的词,缀在煦色韶光后,萦绕不断,忽远忽近地恼着人。 它们常常一闪而逝,行里字间持足了神秘感。偶尔会...
    豆丕阅读 297评论 2 1
  • 时间如流水,我的弟弟——丁丁,也从一个懵懂的小婴儿,成长为一个聪明、掌握多种“技能”的大婴儿。 在温暖、美...
    笑丁妈阅读 1,085评论 4 1
  • ❤ 最近迷上了王小波的《爱你就像爱生命》,看王小波给李银河写的每一封信都诉说着爱意。突然,我就变得好难过,而这种难...
    满嘴胡话_阅读 1,116评论 24 19