• 《lua设计与实现》第4章 表


        使用表来统一表示Lua中的一切数据,是Lua区分其他语言的一个特色。

        Lua表分为数组(索引从1开始)和散列表两个部分,可以模拟其他各种数据--数组、链表、树等。

    4.1 表的数据结构

    //llimits.h:27
    typedef unsigned char lu_byte;
    
    //ltm.h:18
    typedef enum {
      TM_INDEX, TM_NEWINDEX, TM_GC, TM_MODE,
      TM_EQ,  /* last tag method with `fast' access */
      TM_ADD, TM_SUB, TM_MUL, TM_DIV, TM_MOD, TM_POW,
      TM_UNM, TM_LEN, TM_LT, TM_LE, TM_CONCAT, TM_CALL,
      TM_N        /* number of elements in the enum */
    } TMS;
    
    //lobject.h:338
    typedef struct Table {
      CommonHeader;
      lu_byte flags;           /* 1<<p means tagmethod(p) is not present. p对应TMS中的值*/ 
      lu_byte lsizenode;       /* log2 of size of `node' array. size 最大 2^(2^16 - 1) */
      struct Table *metatable; // 存放该表的元表
      TValue *array;           /* array part */
      Node *node;
      Node *lastfree;          /* any free position is before this position */
      GCObject *gclist;
      int sizearray;           /* size of `array' array. size 最大 2^32 - 1  */
    } Table;
    
    //lobject.h:323
    //Node 中含两个成员
    typedef union TKey {
      struct {
        TValuefields;
        struct Node *next;  /* for chaining */
      } nk;
      TValue tvk;
    } TKey;
    
    
    typedef struct Node {
      TValue i_val;
      TKey i_key;
    } Node;

      

     4.2 操作算法

        (1)新增元素

        散列表部分:首先计算数据的key所在的桶的位置(mainposition),即使用luaH_set 、 luaH_setnum 、 luaH_setstr这3个API函数返回TValue指针。然后由外部的使用者来进行替换操作。当找不到对应的key时,这三个API最终都会调用newkey函数分配并返回一个新的key。

    //ltable.c:189
    // 逐个遍历 nums 数组,获得其范围区间内所包含的整数数量大于 50% 的最大索引, 作为重新散列之后的数组大小(na),
    // 超过这个范围的正整数,就分配到散列桶部分了( computesizes 函数)
    static int computesizes (int nums[], int *narray) {
      int twotoi;  /* 2^i */
      int a = 0;  /* number of elements smaller than 2^i */
      int na = 0;  /* number of elements to go to array part */
      int n = 0;  /* optimal size for array part */
      for (int i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) {
        if (nums[i] > 0) {
          a += nums[i];
          if (a > twotoi/2) {  /* more than half elements present? */
            n = twotoi;  /* optimal size (till now) */
            na = a;  /* all elements smaller than n will go to array part */
          }
        }
        if (a == *narray) break;  /* all elements already counted */
      }
      *narray = n;
      lua_assert(*narray/2 <= na && na <= *narray);
      return na;
    }
    
    //ltable.c:333
    /*在散列表中插入新key*/
    static void rehash (lua_State *L, Table *t, const TValue *ek) {
      int nums[MAXBITS+1];  /* nums[i] = number of keys between 2^(i-1) and 2^i */
      for (int i=0; i<=MAXBITS; i++) nums[i] = 0;  /* reset counts */
      int nasize = numusearray(t, nums);  /* count keys in array part */
      int totaluse = nasize;  /* 整数 key 的个数 */
      totaluse += numusehash(t, nums, &nasize);  /* count keys in hash part */
      nasize += countint(ek, nums);/* count extra key */
      totaluse++;
      /* compute new size for array part */
      int na = computesizes(nums, &nasize);
      /* resize the table to new computed sizes */
      resize(L, t, nasize, totaluse - na);
    }
    
    //ltable.c:399
    /*在散列表中插入新key*/
    static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
      Node *mp = mainposition(t, key);
      if (!ttisnil(gval(mp)) || mp == dummynode) { // 新 key 的 main position 不空
        Node *n = getfreepos(t);  /* get a free place */
        if (n == NULL) {          /* cannot find a free place? */
          rehash(L, t, key);      /* grow table */
          return luaH_set(L, t, key);  /* re-insert key into grown table */
        }
        lua_assert(n != dummynode);
    
        Node *othern = mainposition(t, key2tval(mp));
        if (othern != mp) {
          /* 碰撞的 node 不在它的 main position,将此 node 移到一个空的地方,新 key 移过来 */
          while (gnext(othern) != mp) othern = gnext(othern);  /* find previous */
          gnext(othern) = n;  /* redo the chain with `n' in place of `mp' */
          *n = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
          gnext(mp) = NULL;  /* now `mp' is free */
          setnilvalue(gval(mp));
        } else {
          /* 碰撞的 node 在它的 main position,将新 key 移到一个空的位置 */
          gnext(n) = gnext(mp);  /* chain new position */
          gnext(mp) = n;
          mp = n;
        }
      }
      gkey(mp)->value = key->value; gkey(mp)->tt = key->tt;
      luaC_barriert(L, t, key);
      lua_assert(ttisnil(gval(mp)));
      return gval(mp);
    }

         从上面的代码可以看到, Lua解释器背着我们会对表进行重新散列的动作。所以如果有很多小的表需要创建,就可以预先填充以避免重新散列操作。

        (2)迭代:迭代对外的 API是 Lua H_next 函数,它的伪代码是 :

            在数组部分查找数据:
                查找成功, 则返回该 key 的下一个数据
            否则在散列桶部分查找数据:
                查找成功, 则返回该 key 的下一个数据

        (3)取长度操作: luaH_getn,它的伪代码是 :

      如果表存在数组部分:
        在数组部分二分查找返回位置 i ,其中 i 是满足条件t[i]!= nil 且 t[i + 1] = nil 的最大的i
      否则前面的数组部分查不到满足条件的数据,进入散列表部分查找:
        在散列表部分二分查找返回位置 i ,其中 i 是满足条件 t[i]!= nil 且 t[i + 1] = nil 的最大数据
      因此,使用表时需要注意以下事项 。
      尽量不要将一个表混用数组和散列桶部分,即 一个表最好只存放一类数据 。 Lua 的实现上确实提供了两者统一表示的遍历,但是这不意味着使用者就应该混用这两种方式。
      尽量避免进行重新散列操作,因为重新散列操作的代价极大。 通过预分配、只使用数组部分等策略规避这个Lua解释器背后的动作,能提升不少效率 。
    print(#{10, 20, nil, 40})           --输出 2(书中数据有误), lua 5.1.4实测输出4
    print(#{[1] = 1, [2] = 2 })         --输出 2
    print(#{[1] = 1, [2] = 2, [5] = 5}) --输出 5(书中数据有误), lua 5.1.4实测输出2
    print(#{[1] = 1, [2] = 2, 1, 2, 3}) --输出 3
  • 相关阅读:
    Java多态
    推荐TED演讲:20岁光阴不再来(Why 30 is not the new 20)
    HDU 5305 Friends (DFS)
    C#高级编程八十一天----捕获异常
    Amazon EC2安装mysql多实例并配置主从复制
    python coding style guide 的高速落地实践
    Tomcat 在win7/win8 系统下tomcat-users.xml.new(拒绝访问)解决方法
    jsp+oracle实现数据库内容以表格形式在前台显示(包含分页)
    JSP/SERVLET入门教程--Servlet 使用入门
    解决系统打开CHM文件无法正常显示
  • 原文地址:https://www.cnblogs.com/yyqng/p/14300732.html
Copyright © 2020-2023  润新知