Lua源碼分析-table

table的數據結構

  Lua把 table 的儲存分爲數組部分和哈希表部分。數組部分從 1 開始作整數數字索引。
這可以快速高效的訪問。而不能被儲存在數組部分的數據全部放在哈希表中, nil不能
做哈希鍵值。Lua的哈希表的操作時間複雜度爲O(1)。源碼如下:

lobject.h//
typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ 
  lu_byte lsizenode;  /* log2 of size of `node' array */
  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 */
} Table;

  table結構由三塊連續內存構成。一塊存放整數索引的數組,一塊存放2的整數次冪大小的哈希表。哈希表的最小尺寸爲2的0次冪。對於空的哈希表,用dummynode初始化。

table的算法

  Lua實現了四種基本操作:讀、寫、迭代和獲取長度。Lua中並沒有刪除操作,而僅僅是把對應鍵位的值設置爲nil。table中的數組實現和C的數組是一樣,哈希表則是用閉散列實現的。下面是哈希表的具體實現。

  table中的哈希表的每一個鍵位都會有一個主位置,當在加入一個新的鍵值對時,會先檢查新的鍵是否和已有鍵衝突,沒有衝突則直接設值。當發生衝突是,會先檢查已有的鍵值是否是主位置,如果是主位置,則將新鍵已一個單鏈表的形式鏈接到主鍵的next域上,否則新鍵佔據當前位置,通過lastfree來從後往前進行查找,找到一個空位後把舊的鍵值對放在這裏。當沒有空的位置時,則觸發rehash方法,重新分配table的大小。當賦nil值的時候,Lua也不會回收空間,而是當準備好的哈希表空間用完的時候,也就是lastfree遞減到哈希表空間頭的時候,觸發rehash操作。源碼如下:
  


static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
  Node *mp = mainposition(t, key);
  if (!ttisnil(gval(mp)) || mp == dummynode) {
    Node *othern;
    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);
    othern = mainposition(t, key2tval(mp));
    if (othern != mp) {  /* is colliding node out of its main position? */
      /* yes; move colliding node into free position */
      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 {  /* colliding node is in its own main position */
      /* new node will go into free position */
      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);
}

  rehash 的主要工作是統計當前table中到底有多少有效鍵值對,以及決定數組部分需要開闢多少空間。其原則是最終數組部分的利用率需要超過50%。lua使用一個rehash函數中定義在棧上的nums數組來做這個整數鍵統計工作。這個數組按2的整數冪次來分開統計各個區段間的整數鍵個數。統計過程的實現見numusearray和numusehash函數。最終,computesizes函數計算出不低於50%利用率下,數組空間的大小。同時,還可以得到有多少有效鍵將被儲存在哈希表裏。根據這些統計數據,rehash函數調用luaH_resize 這個 api 來重新調整數組部分和哈希部分的大小,並把不能放在數組裏的鍵值對重新塞入哈希表。源碼如下:
  


static void rehash (lua_State *L, Table *t, const TValue *ek) {
  int nasize, na;
  int nums[MAXBITS+1];  /* nums[i] = number of keys between 2^(i-1) and 2^i */
  int i;
  int totaluse;
  for (i=0; i<=MAXBITS; i++) nums[i] = 0;  /* reset counts */
  nasize = numusearray(t, nums);  /* count keys in array part */
  totaluse = nasize;  /* all those keys are integer keys */
  totaluse += numusehash(t, nums, &nasize);  /* count keys in hash part */
  /* count extra key */
  nasize += countint(ek, nums);
  totaluse++;
  /* compute new size for array part */
  na = computesizes(nums, &nasize);
  /* resize the table to new computed sizes */
  resize(L, t, nasize, totaluse - na);
}

查詢操作 luaH_get 的實現要簡單的多。當查詢鍵爲整數鍵且在數組範圍內時,在數組部分查詢;否則,根據鍵的哈希值去哈希表中查詢。擁有相同哈希值的衝突鍵值對,在哈希表中由 node 的 next域單向鏈起 來,所以遍歷這個鏈表就可以了。源碼如下:


const TValue *luaH_get (Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TNIL: return luaO_nilobject;
    case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key));
    case LUA_TNUMBER: {
      int k;
      lua_Number n = nvalue(key);
      lua_number2int(k, n);
      if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */
        return luaH_getnum(t, k);  /* use specialized version */
      /* else go through */
    }
    default: {
      Node *n = mainposition(t, key);
      do {  /* check whether `key' is somewhere in the chain */
        if (luaO_rawequalObj(key2tval(n), key))
          return gval(n);  /* that's it */
        else n = gnext(n);
      } while (n);
      return luaO_nilobject;
    }
  }
}

table的迭代

  Lua中table的迭代是通過luaH_next方法實現的,出入上一個鍵,返回下一個鍵值對。這個方法會先在數組部分查找這個key,當超過數組部分後,就會在哈希表中查找,找到後返回對應節點在存儲空間中的下一個節點處的鍵值對。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章