Lua中table的實現-《Lua設計與實現》


本文來自《Lua設計與實現》的閱讀筆記,推薦Lua學習者可以購買一本,深入淺出講解lua的設計和實現原理,很贊,哈哈
 
Lua中對於表的設計,是基於數組和散列表,和其他語言不同,對於數組的下標是從1開始的,對於散列表而言,只要其鍵值補位nil,都可以存儲在其中。
 
一、table的基本類型定義
首先看看table的數據定義,參考源碼lobject.h
 
CommonHeader, 參看專欄前面的文章;
flags 這是一個lua的byte類型的數據,用於表示表中提供了哪些元方法,比如是否提供了元方法_index,該數據最開始設置為1,如果進行查找一次,比如_index,如果存在,這該元方法對應的flag bit設置為0,在下一次查找的時候,只需要比較這個bit即可,對應的元方法在ltm.h中
lsizenode,為散列表的大小,必定為2的冪對應的數字;
metatable,該table的元表;
array,該table的數組的指針
node, 該table的散列表的起始位置的指針;
lastfree, 該散列表的最后位置的指針
gclist, gc相關的鏈表
sizearray, 數組的大小,不一定為2的冪對應的數字
對於node數據,類似於其他語言中的字典設計\hash設計,就是一個鍵值對集合,其定義為:
需要提一下的是對於key的設計采用的是union,也就是說Lua的散列表的key,可以為nk對應的struct,也可以是TValue類型
 
二、table相關的操作的實現原理
1、查找算法的實現原理
借用原文的偽代碼:
if 輸入的key為整數 && key >= 0 && key <= 數組的大小
嘗試在數組部分查找
else 在散列表部分查找
計算出該key的散列值,據其查找對應的node所在散列表中的位置,然后遍歷其對應的鏈表,查找是否有該key對應的元素
舉例:
local t = {}
t[1] = 0
t[100] = 0
那么1是在數組中查找,100就是在散列表中去查找了(100大於數組的len)
 
2、新增元素的實現原理
給lua中添加新元素的時候,會有可能觸發重新分配table中的數組和散列表,其本質來自於散列表的rehash(由於lua對於下標超過數組的大小的數字,都會存儲在散列表部分去,所以數組部分的插值不會觸發rehash)
散列表的組織,就是多個mainposition,每個單獨的mainposition會對應一個數據鏈表,當插入一個key的時候,會調用luaHset\luaH_setnum\luaH_setstr,來獲得該key對應的TValue指針,如果沒有,則調用內部的newkey函數來分配一個新的key:
基本的實現過程看源代碼寫的比較詳細,這兒說一下rehash部分的操作,在ltable.c中:
1) nums中存放的是元素的數量
2)分表遍歷數組(numusearray)和散列表(numusehash),統計更新nums中的數量大小
3) 重新計算數組和hash部分的大小,數組大小的計算規則:逐個遍歷nums數組,獲得其范圍區間內所包含的整數數量大於50%的最大索引,作為rehash后的數組大小,這個索引值來自與computesizes函數:
可能看了會有點迷糊,那我就用大白話說一下吧:
首先nums數組在統計后,每個下標對應的是處於當前2^(i -1) - 2^i中的元素的個數,然后不斷的累加計算,求得滿足 sum > 2^n/2的最大下標值(這個下標值是nums數組中的)
所以,在不同的rehash階段,table中的同一個key可能會在數組部分和散列表部分交替出現,也是可能的。
由於rehash會帶來較大的性能消耗,所以一般都盡量避免,比如在創建表的時候,就采用預填充的算法
 
3、取長度算法的原理
如果table中元表沒有重載len方法,則調用的是luaH_getn方法,其基本的偽代碼為:
if 表中存在數組部分:
初始化i = 0, j = sizearray
  while(j - i > 1){
    m = (j + i)/2
  if(array[m-1] == nil)
    j = m
  else
    i = m
  }
  return i
else
  查找表中散列表長度,算法同數組部分
對於表中只有散列表的時候,其實質就是對鍵值為正整數的部分進行長度操作,如果既有數組,又有散列表,則優先對數組部分進行長度操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM