由淺入深的理解Lua的數(shù)據(jù)結(jié)構(gòu)——table

1.Table的幾個小知識點

  • table使用關(guān)聯(lián)數(shù)組惧盹,我們可以使用任意類型的值來作為數(shù)組的索引(不能使用nil)
  • table沒有固定的大小乳幸,可以動態(tài)的添加元素

2.table的構(gòu)造

//初始化表
//1
mytable = {}
//2
_mytable = {a=100,b="123"}

//使用.號賦值
_mytable.a = 110
//使用索引賦值
_mytable["c"]=139

思考一下:如果現(xiàn)在定義了一個table a,將table a賦值給table b钧椰,此時它們的內(nèi)存情況是什么樣呢反惕?
ab都會指向同一個內(nèi)存塊,如果a設(shè)置為nil演侯,b依舊能訪問該內(nèi)存塊的元素姿染,直到b設(shè)置為nil后,Lua的垃圾回收機制會清理相應(yīng)的內(nèi)存秒际。所以當(dāng)b在更改table內(nèi)的值后悬赏,a再去訪問的時候,值也是改變的娄徊。

ab內(nèi)存

table的for循環(huán)寫法

for k,v in pairs(mytable) do
  print(i,v)
end

3. 從table結(jié)構(gòu)體分析table屬性

typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ 
  lu_byte lsizenode;  /* 以2的lsizenode次方作為哈希表長度 */
  struct Table *metatable /* 元表 */;
  TValue *array;  /* 數(shù)組 */
  Node *node; /* 哈希表 */
  Node *lastfree;  /* 指向最后一個為閑置的鏈表空間 */
  GCObject *gclist;
  int sizearray;  /* 數(shù)組的大小 */
} Table;

先解釋一下上面的各個變量

  1. CommonHeader 為所有可回收資源提供標(biāo)記頭
  2. lu_byte其實是 typedef unsigned char lu_byte闽颇,lu_byte flags用于表示表中提供了哪些元方法(元方法是什么?我們下面和元表一起解釋)
  3. lu_byte lsizenode 它的值就是表的長度寄锐,但是散列表大小的擴增一定是2的冪兵多,如果散列桶數(shù)組要擴展的話尖啡,也是每次在原大小的基礎(chǔ)上乘以2的形式擴展。
  4. struct Table *metatable 也就是元表剩膘。

3.1 元表
3.1.1 為什么要用元表

在table中衅斩,我們無法對兩個table之間進行操作,比如:table a+ table b怠褐。為了解決這個問題畏梆,就引入了元表的概念,它允許我們改變table的行為奈懒。

3.1.2 元表的操作流程

當(dāng)我們在進行表a+b的時候:

  1. 檢查兩者中是否至少有一者是有元素
    2.查找是否有"_add"字段奠涌,如果有,就取得它的值磷杏,此時這個值就是我們的元方法溜畅,里面編寫了add函數(shù)

其實逼逼了這么多,我覺得元表和元方法其實就相當(dāng)于重載极祸,比如_add慈格,我們就是重載了+操作符
也可以將它理解成事件驅(qū)動,元表中的鍵對應(yīng)著不同的事件名贿肩,關(guān)聯(lián)的值是元方法峦椰,元方法里就是我們事件對應(yīng)的操作。


繼續(xù)接上面的分析

  1. TValue *array是指向數(shù)組的指針
    TValue是一個結(jié)構(gòu)體汰规,里面保存著TValuefields汤功,它是一個宏定義,包含value和int值溜哮,而value是一個聯(lián)合體滔金,它可以是指針、數(shù)字等等茂嗓。
typedef struct lua_TValue {
  TValuefields;
} TValue;

TValuefields:
#define TValuefields    Value value; int tt

Value:
typedef union {
  GCObject *gc;
  void *p;
  lua_Number n;
  int b;
} Value;
  1. Node *node 是指向該表的散列桶數(shù)組起始位置的指針

3.2 Node類型

每個Node都是一個鍵值對,里面包含了key和value餐茵。tvk是key的值,但是當(dāng)我們出現(xiàn)hash沖突述吸,此時lua的hash算法比較特殊忿族,一般情況下,我們的hash算法都是根據(jù)key算出hash蝌矛,然后如果有沖突的話道批,就放在改位置的鏈表上。而lua不同入撒,當(dāng)它出現(xiàn)hash沖突的時候隆豹,會在hash表中找到一個空的位置x,來存放key茅逮,并且讓沖突處的節(jié)點的nk.next指向x璃赡。
這意味著什么呢判哥?發(fā)生沖突我們無需重新分配空間來存儲沖突的key,而是利用hash表上未用過的空白處來存儲碉考。剛才我們將key放在了空位置x塌计,如果此時存在另一個key2,它算出的hash值是空位置x豆励,而我們剛才的key只是因為hash沖突了夺荒,占用了其他key的位置瞒渠,這個時候我們就講key2放在x上良蒸,將key再放到另一個空白處

忍不住想總結(jié)一波table的實現(xiàn)伍玖,和上面我們的Node類型進行對照

3.3 table的實現(xiàn)

lua的table其實由數(shù)組段hash兩部分組成嫩痰,當(dāng)你的key值不會過于離散的時候,lua就會將它存儲在數(shù)組段(也就是下圖的array)窍箍,反正會存儲在hash段(也就是下圖的node)串纺,這個分割線是以數(shù)組段的利用率不低于50%為準(zhǔn)。hash段采用閉散列的算法椰棘,它將有沖突的key存儲在空閑槽中纺棺,而不額外分配內(nèi)存。

table

在我們table結(jié)構(gòu)體中邪狞,array和sizearray都是表示數(shù)組段祷蝌。
而lsizenode和node,lastfree是表示hash段帆卓。node指向hash表的起始位置巨朦,lsizenode是log2(node指向的哈希表的節(jié)點數(shù)目),lastfree指向node里面最后一個未使用的節(jié)點(因為我們在hash沖突的時候剑令,是從后往前查找未使用的節(jié)點糊啡,lastfree存儲最后一個未使用節(jié)點就可以方便查找)


如果此時hash表中已經(jīng)沒有空格了,那么lua就會resize這個hash表(等會再談lua的動態(tài)擴增)

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. table的創(chuàng)建吁津、查找棚蓄、動態(tài)分配

4.1 table的創(chuàng)建
Table *luaH_new (lua_State *L, int narray, int nhash) {
  Table *t = luaM_new(L, Table);
  luaC_link(L, obj2gco(t), LUA_TTABLE);
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  /* temporary values (kept only if some malloc fails) */
  t->array = NULL;
  t->sizearray = 0;
  t->lsizenode = 0;
  t->node = cast(Node *, dummynode);
  setarrayvector(L, t, narray);
  setnodevector(L, t, nhash);
  return t;
}

lua創(chuàng)建新表的時候先為新表分配內(nèi)存Table * t = luaM_new(L, Table),然后將表連接到gc上并設(shè)置標(biāo)志位luaC_link(L, obj2gco(t), LUA_TTABLE)碍脏,然后初始化一些必要的屬性梭依,使用setarrayvector為數(shù)組段分配內(nèi)存,setnodevector為hash部分分配內(nèi)存潮酒,最后返回表指針睛挚。

4.2 table的查找
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的查找會根據(jù)key進行判斷,如果key為空就直接返回空急黎,key為字符串就調(diào)用luaH_getstr(t, rawtsvalue(key))扎狱,key為數(shù)字則根據(jù)它是否整數(shù)調(diào)用luaH_getnum(t, k)侧到,否則,計算出key的位置淤击,遍歷table的node節(jié)點匠抗,找到對應(yīng)鍵所在的節(jié)點返回。

因為key為數(shù)字比較特殊污抬,所以研究一把luaH_getnum函數(shù)的實現(xiàn)

const TValue *luaH_getnum (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;
  }
}

如果key大于等于1汞贸,小于數(shù)組的長度,則從數(shù)組中取出對應(yīng)的鍵值印机,否則利用hashnum找到key對應(yīng)的node位置矢腻,遍歷node鏈表,返回對應(yīng)的值

4.3 table的賦值
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);
}

dummynode是帶頭結(jié)點的指針射赛。
往table中插入新值多柑,先檢測key的主位置(main position)是否為空,主位置就是key的哈希值在node中的位置楣责。
如果主位置為空竣灌,就直接插入,主位置不為空秆麸,檢查占領(lǐng)該位置的key的主位置是不是在這個地方初嘹,如果不在,則將該key移動到其他空閑位置沮趣,將要插入的key插入到這個位置中屯烦。如果在這個地方,則將要插入的key插入到一個空槽中兔毒。
如果找不到空閑位置放新鍵值漫贞,就rehash函數(shù),擴增hash表的大小育叁,再找出新位置迅脐,再調(diào)用luaH_set把要插入的key插入到新的哈希表中,直接返回LuaH_set的結(jié)果豪嗽。

4.4 table的動態(tài)增長
4.4.1 rehash
//加入key谴蔑,重新分配hash與array的空間
static void rehash (lua_State *L, Table *t, const TValue *ek) {
  int nasize, na;//nasize前期累計整數(shù)key個數(shù),后期做為數(shù)組空間大小龟梦,na表示數(shù)組不為nil的個數(shù)
  int nums[MAXBITS+1];  /* 累計各個區(qū)間整數(shù)key不為nil的個數(shù)隐锭,包括hash, 例如nums[i]表示累計在[2^(i-1),2^i]區(qū)間內(nèi)的整數(shù)key個數(shù)*/
  int i;
  int totaluse;//記錄所有已存在的鍵计贰,包括hash和array钦睡,即table里的成員個數(shù)
  for (i=0; i<=MAXBITS; i++) nums[i] = 0;  /* 初始化所有計數(shù)區(qū)間*/
  nasize = numusearray(t, nums);  /* 以區(qū)間統(tǒng)計數(shù)組里不為nil的個數(shù),并獲得總數(shù)*/
  totaluse = nasize;  /* all those keys are integer keys */
  totaluse += numusehash(t, nums, &nasize);  /* 統(tǒng)計hash表里已有的鍵躁倒,以及整數(shù)鍵的個數(shù)已經(jīng)區(qū)間分布*/
  //如果新key是整數(shù)類型的情況
  nasize += countint(ek, nums);
  //累計新key
  totaluse++;
  /* 重新計算數(shù)組空間 */
  na = computesizes(nums, &nasize);
  /* 重新創(chuàng)建內(nèi)存空間, nasize為新數(shù)組大小
//totaluse - na表示所有鍵的個數(shù)減去新數(shù)組的個數(shù)荞怒,即為新hash表需要存放的個數(shù) */
  resize(L, t, nasize, totaluse - na);
}
4.2 resize
static void resize (lua_State *L, Table *t, int nasize, int nhsize) {
  int i;
  int oldasize = t->sizearray;
  int oldhsize = t->lsizenode;
  Node *nold = t->node;  /* 保存當(dāng)前的hash表洒琢,用于后面創(chuàng)建新hash表時,可以重新對各個node賦值*/
  if (nasize > oldasize)  /* 是否需要擴展數(shù)組 */
    setarrayvector(L, t, nasize);
  /* 重新分配hash空間*/
  setnodevector(L, t, nhsize);  
  if (nasize < oldasize) {  /* 小于之前大小褐桌,即有部分整數(shù)key放到了hash里 */
    t->sizearray = nasize;
    /* 超出部分存放到hash表里*/
    for (i=nasize; i<oldasize; i++) {
      if (!ttisnil(&t->array[i]))
        setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]);
    }
    /* 重新分配數(shù)組空間衰抑,去掉后面溢出部分*/
    luaM_reallocvector(L, t->array, oldasize, nasize, TValue);
  }
  /* 從后到前遍歷,把老hash表的值搬到新表中*/
  for (i = twoto(oldhsize) - 1; i >= 0; i--) {
    Node *old = nold+i;
    if (!ttisnil(gval(old)))
      setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old));
  }
  //釋放老hash表空間
  if (nold != dummynode)
    luaM_freearray(L, nold, twoto(oldhsize), Node);  /* free old array */
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荧嵌,一起剝皮案震驚了整個濱河市呛踊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啦撮,老刑警劉巖谭网,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逻族,居然都是意外死亡蜻底,警方通過查閱死者的電腦和手機骄崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門聘鳞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人要拂,你說我怎么就攤上這事抠璃。” “怎么了脱惰?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵搏嗡,是天一觀的道長。 經(jīng)常有香客問我拉一,道長采盒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任蔚润,我火速辦了婚禮磅氨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫡纠。我一直安慰自己烦租,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布除盏。 她就那樣靜靜地躺著叉橱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪者蠕。 梳的紋絲不亂的頭發(fā)上窃祝,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音踱侣,去河邊找鬼粪小。 笑死甩栈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糕再。 我是一名探鬼主播量没,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼突想!你這毒婦竟也來了殴蹄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猾担,失蹤者是張志新(化名)和其女友劉穎袭灯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绑嘹,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡稽荧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了工腋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姨丈。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擅腰,靈堂內(nèi)的尸體忽然破棺而出蟋恬,到底是詐尸還是另有隱情,我是刑警寧澤趁冈,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布歼争,位于F島的核電站,受9級特大地震影響渗勘,放射性物質(zhì)發(fā)生泄漏沐绒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一旺坠、第九天 我趴在偏房一處隱蔽的房頂上張望乔遮。 院中可真熱鬧,春花似錦价淌、人聲如沸申眼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽括尸。三九已至,卻和暖如春病毡,著一層夾襖步出監(jiān)牢的瞬間濒翻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留有送,地道東北人淌喻。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像雀摘,于是被迫代替她去往敵國和親裸删。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,835評論 0 38
  • 以人心為鏡阵赠,照他人之丑陋涯塔,照己之齷齪。
    從頁石生閱讀 155評論 0 0
  • IIC是什么?IIC是Inter-Integrated Circuit(內(nèi)部集成電路)的簡寫清蚀,是一種串行通信總線匕荸,...
    偷天神貓閱讀 248評論 0 1
  • 疲憊的一天結(jié)束了!靜下心來寫寫日記枷邪,當(dāng)拿起手機的時侯榛搔,突然發(fā)現(xiàn)腦子一片空白,不知道該怎么寫了东揣,心里萌生了放棄的念頭...
    劉嘉禾爸爸閱讀 263評論 4 4
  • 作者: 鱘余 害怕践惑。被吸收的恐懼堵塞了夜空 微微顫抖的黑洞還給星光一場夢 難過。被腐蝕的天使雕像與刀痕 在城市上方...
    鱘余閱讀 504評論 3 1