深度剖析Lua Table的運作方式

前言:本篇基于Lua-5.3.6源碼并配合《Lua 解釋器構建:從虛擬機到編譯器》一書進行Table的運作解讀。

一、Table數據結構

typedef struct Table {CommonHeader;lu_byte flags;  /* 1<<p means tagmethod(p) is not present */lu_byte lsizenode;  /* log2 of size of 'node' array */unsigned int sizearray;  /* size of 'array' array */TValue *array;  /* array part */Node *node;Node *lastfree;  /* any free position is before this position */struct Table *metatable;GCObject *gclist;
} Table;

如圖:

CommonHeader:? ?GC的公共頭部,標識是GC管理的對象,Table也是一個GC對象。

flags:標記缺失的元方法。比如:flags&(1<<TM_INDEX)為真表示缺少_index元方法。

lsizenode:哈希表大小的log2值,一來方便表示更大的存儲容量,二來哈希表的擴容是成2倍增長的。

arraysize:數值的大小。

array:數組部分的指針。

node:哈希表部分的指針。

lastfree:哈希表空閑位置標記(指向最后一個空閑節點)

metatable:Table的元表,用來定義特定對象的元方法(如 __index、__newindex、__add 等)。

gclist:GC鏈表指針。

可以看到Table實際上是由兩部分組成,數組部分哈希表部分。

二、鍵值的哈希計算

在完成了對key的哈希運算以后,就需要根據得到的哈希值,將其換算成表結構node數組的索 引值,計算的公式如下。

index=hash_value&(2^{lsizenode}-1)

這里-1是希望hash的低位全是1。

舉例:假設有個字符串為"table",計算它在大小為8的哈希表的索引。

假設根據方法已經得到哈希值,01101011 00100100 10001101 00101100

lsizenode=\log_{2}8=3

那最終相與 只看后四位的結果? 1100&0111=0100=4

key為“table"的node,將會被定位到hash[4]的位置上

三、Table查找元素

分兩種情況,key值是int和非int

a.key是int

1)令被查找元素的key值為k,表array數組的??為arraysize。

2)判斷被查找元素的key值是否在數組范圍內(即k≤arraysize是否成?)。

3)若key值在表的數組范圍內,則返回array[k-1],流程終?。

4)若key值不在表的數組范圍內,計算key值在哈希表中的位置,計算?式為

index=k&(2^{lsizenode}-1),然后以hash[index]節點為起點,查找key值與k相等的node,如果沒找到則返回nil。

b.key是非int

1)計算被查詢元素的key值(記為k)的哈希值,記為key_hash。

2)計算key值在哈希表中的位置,計算?式為index=k&(2^{lsizenode}-1),然后以 hash[index]節點為起點,查詢key值與k相等的node,如果沒找到則返回nil。

四、Table更新和插入

1)假設要新建的key值為k,計算k的哈希(hash)值,記為k_hash

2)計算key值在哈希表的索引,計算?式為index=k_hash &(2lsizenode-1)。

3)如果hash[index]的value值為nil,將其的key值設置為k的值,并返回value_對象指針,供調 ?者設置。

4)如果hash[index]的value值不為nil,需要分以下兩種情況處理。

a. 計算node key的hash值,重新計算它的索引值。如果計算出來的索引位置不是hash[index], 那么lastfree不斷左移,直?找到?個空閑的節點。將其移動到這?,修改鏈表關系,令其上? 個與??索引值相同節點的next值指向??(如果存在的話)。新插?的key和value設置到 hash[index]節點上。

b. 計算node key的hash值,重新計算它的索引值,如果計算出來的索引位置就是hash[index], 那么lastfree不斷左移,直?找到?個空閑的節點。將新插?的key和value值設置到這個節點 上,并調整鏈表關系,將hash[index]的key值的next值指向新插?的節點。

舉例:

向表插??個 key值為5、value值為“xixi”的元素。

由于key值5超出了數組的??范圍,那么程序?先會嘗試 去哈希表中查找,可以得到最終index的值為1。hash[1]的key值為nil,與要更新元素的key值不 相等,于是觸發了插?操作。由于hash[1]的key和value值均是nil,因此可以將該元素直接設置 到這?。

向表插??個 key值為13、value值為“manistein”的元素。

根據公式算出index為1,因為hash[1]的value 域的值為“xixi”、key值為5,與k值13并不相等,于是便發?了哈希碰撞。key值5經過轉換運算 得到的哈希表index的值為1,此時它就在這個位置上,因此key值為13的新元素需要被移?。 lastfree指針向左移動,并且將key值為13、value值為“manistein”的元素賦值到lastfree指向的位置上(即hash[3]的位置上),并且將hash[1]的key的next指向lastfree指針所指的位置。

注意:這里的next值指的是與當前index相隔的距離,而不是下一個節點的index值,因為這里的哈希表本身是個鏈表,存index值沒有意義

向表插??個 key值為7、value值為“wu”的元素。

經過計算得到其對應的hash表 index值為3,此時hash[3]已經被占?。此時需要計算占據在這?的元素的key值,其真實對應 的hash表index其實是1,因為hash[1]被占?才被移動到這?。因為這個元素計算得到的index 與當前位置并不匹配,因此lastfree指針需要繼續向左移動,并將key值為13的元素遷移到這 ?,并更新其前置節點的next域。最后將key值為7的元素,賦值到hash[3]的位置上.

五、Table擴容機制

更新和插?的操作,均是在哈希表空間充?的情況下進?的,當哈希表 已滿,且?有新的元素要插?哈希表時,將觸發表的resize操作。首先調整的是數組的大小

1)統計要調整??的Lua表、數組和哈希表中的有效元素(值類型不為nil的元素)的總數

2)創建?個int類型的數組,它的??為32,將其命名為nums。nums [i]表示的信息量?較 ?。?先i表示?個數值區間,這個區間是(2^{i-1}2^{i}]

3)統計數組的分布情況,假設arraysize是66,且每個Value都不為空,那么此時它的分布情況是

66=2^{0}+2^{1}+2^{2}+2^{3}+2^{4}+2^{5}+3

4)統計哈希表元素在nums [32]中不同區間的分布情況,偽代碼如下

//lsizenode是Table數據結構中衡量哈希表長度的變量
for(int i=0;i<pow(2,lsizenode);i++){if(hash[i].key!=null&&isInt(hash[i].key)){int k=ceillog2(hash[i].key);nums[k]++;}
}//找到hash key值在Nums數組中的下標
void ceillog2(int hashKey){for(int i=0;i<32;i++){if(pow(2,i)>=hash.key){return i;}}
}

5)判斷新插?元素new_element的key值是否為整類型,如果是則令

nums [FindIndex (new_element.key)]++。

6)完成數組nums的統計之后,根據nums計算新的數組??。在數組??范圍內,值不為nil的 元素要超過數組??的?半,其計算公式如下。

int asize=0;
for(int i=0;i<32;i++){asize+=nums[i];if(asize>pow(2,i)/2){sizearray=pow(2,i); //sizearray是Table中衡量數組大小的變量}
}

7)計算在數組??范圍內有效元素的個數,記為array_used_num。

8)當數組???原來?時,擴展原來的數組到新的??,并將哈希表中key值≤arraysize,且 >0的元素轉移到數組中,并將哈希表??調整為ceillog2(total_element-array_used_num), 同時對每個node進?重新定位位置。

數組擴大的簡單理解:數組部分加入哈希表里面可以轉移到數組里的鍵值對,此時數組部分超過一半都是不為nil。

示例:

9)當數組???原來?時,縮?原來的數組到新的??,并將數組中key值超過數組??的元 素轉移到哈希表中。此時哈希表??調整為ceillog2(total_element-array_used_num),同時 對每個node進?重新定位位置。

數組縮小的簡單理解:數組不連續的key轉移到哈希表,此時數組超過一半都是nil。

基于8-9至此我們可以推理到一個二級結論:

哈希表里最新的int的類型key值一定大于數組長度(sizearray)。

這個結論對于后續Table的遍歷起到了一定的理論依據。

六、Table遍歷

Lua提供了luaH_next函數來進?迭代操作,函數申明如下

方法中關鍵調用是findIndex方法,5.3源碼如下

/*
** returns the index of a 'key' for table traversals. First goes all
** elements in the array part, then elements in the hash part. The
** beginning of a traversal is signaled by 0.
*/
static unsigned int findindex (lua_State *L, Table *t, StkId key) {unsigned int i;if (ttisnil(key)) return 0;  /* first iteration */i = arrayindex(key);if (i != 0 && i <= t->sizearray)  /* is 'key' inside array part? */return i;  /* yes; that's the index */else {int nx;Node *n = mainposition(t, key);for (;;) {  /* check whether 'key' is somewhere in the chain *//* key may be dead already, but it is ok to use it in 'next' */if (luaV_rawequalobj(gkey(n), key) ||(ttisdeadkey(gkey(n)) && iscollectable(key) &&deadvalue(gkey(n)) == gcvalue(key))) {i = cast_int(n - gnode(t, 0));  /* key index in hash table *//* hash elements are numbered after array ones */return (i + 1) + t->sizearray;}nx = gnext(n);if (nx == 0)luaG_runerror(L, "invalid key to 'next'");  /* key not found */else n += nx;}}
}

細分key值四種情況:

情況一:key=nil

返回數組的第一個元素。

情況二:key=整型&&key<sizearray

返回數組的下一個元素。

情況三:key=整型&&key==sizearray

返回哈希表的第一個元素。

情況四:key!=整型

返回哈希表的下一個元素。

七、解讀和Table相關的接口

1)pairs和ipairs

  • pairs(t):用于遍歷表中所有鍵值對(無序),默認使用內建的 next 函數遍歷所有鍵。順序不保證,對稀疏表或散列部分都能遍歷到。
  • ipairs(t):用于按整數索引從 1 開始順序遍歷,直到遇到第一個 nil 為止。常用于“數組風格”的連續整數索引。

注意:ipairs并不僅僅遍歷array部分,hash部分也會遍歷,因為由前文可得,hash部分也會存在連續的整型key。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94397.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94397.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94397.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PETR/PETRv2

PE: position embedding 一、PETR算法動機回歸 1.1 DETR 輸入組成&#xff1a;包含2D位置編碼和Object Query 核心流程&#xff1a;通過Object Query直接索引2D特征圖&#xff0c;結合位置編碼迭代更新Query 特點&#xff1a;整體流程簡潔&#xff0c;每個Query代表一個潛在目標…

計算機大數據畢業設計推薦:基于Spark的氣候疾病傳播可視化分析系統【Hadoop、python、spark】

精彩專欄推薦訂閱&#xff1a;在下方主頁&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主頁&#xff1a;計算機畢設木哥&#x1f525; &#x1f496; 文章目錄 一、項目介紹二、…

英偉達顯卡GPU驅動的本質

我們來深入、詳細地探討一下英偉達&#xff08;NVIDIA&#xff09;GPU驅動程序的本質。 普通用戶眼中的驅動程序可能只是一個“讓顯卡工作的軟件”&#xff0c;但它的本質遠比這復雜和深刻。我們可以從幾個層面來理解它。 核心比喻&#xff1a;翻譯官、指揮官與優化大師 如果說…

算法 ---哈希表

一、哈希介紹 是什么 存儲數據的容器 什么用 快速查找某個元素 什么時候用哈希表 頻繁的查找某一個數的時候 怎么用哈希表 &#xff08;1&#xff09;容器&#xff08;哈希表&#xff09; &#xff08;2&#xff09;用數組模擬哈希表&#xff08;字符串的字符&#xf…

基于分布式環境的令牌桶與漏桶限流算法對比與實踐指南

基于分布式環境的令牌桶與漏桶限流算法對比與實踐指南 在高并發的分布式系統中&#xff0c;限流是保障服務可用性和穩定性的核心手段。本文聚焦于令牌桶算法與漏桶算法在分布式環境下的實現與優化&#xff0c;對多種解決方案進行橫向對比&#xff0c;分析各自的優缺點&#xff…

WPF MVVM入門系列教程(TabControl綁定到列表并單獨指定每一頁內容)

在以前的開發過程中&#xff0c;對于TabControl控件&#xff0c;我一般是習慣直接定義TabItem&#xff0c;在TabItem下布局&#xff0c;并進行綁定。 類似這樣 1 <TabControl ItemsSource"{Binding TabList}" SelectedIndex"0">2 <TabItem…

L2CAP 面向連接信道(CoC)在 BLE 中的應用:建立、流控與數據傳輸

在物聯網(IoT)蓬勃發展的今天,低功耗藍牙(BLE)技術因其高效節能、低成本等特性,成為短距離無線通信的首選方案。作為 BLE 協議棧的核心組件,邏輯鏈路控制與適配協議(L2CAP)的面向連接信道(CoC)承擔著數據傳輸的關鍵任務。本文將深入解析 L2CAP CoC 在 BLE 中的應用,…

醫療AI與醫院數據倉庫的智能化升級:異構采集、精準評估與高效交互的融合方向(上)

摘要: 隨著醫療信息化建設的深入,醫院數據倉庫(Data Warehouse, DW)作為醫療AI應用的核心數據底座,其效能直接決定智能化轉型的深度與廣度。本文聚焦醫療AI驅動下醫院數據倉庫的三大關鍵升級功能——異構采集支持數據庫體檢與智能SQL分析、評估引擎重構實現六大數據庫精準…

2015-2018年咸海流域1km歸一化植被指數8天合成數據集

數據集摘要數據集包含2015年-2018年咸海流域NDVI 8天均值數據。提取美國國家航空航天局中分辨率成像光譜儀MOD13A2產品第一波段作為歸一化植被指數數據&#xff0c;乘以比例因子0.0001&#xff0c;疊加咸海流域邊界數據&#xff0c;裁切后得到咸海流域范圍內的NDVI月均值數據。…

Kafka消息持久化機制全解析:存儲原理與實戰場景

目錄 引言? 一、Kafka消息持久化的核心目標 二、底層存儲機制深度剖析 1.【文件系統分層】——日志分組 日志段 核心結構 示例目錄結構 2.【消息寫入流程】——從內存到磁盤的旅程?? 3.【默認存儲參數】——生產環境的黃金比例 三、典型應用場景與案例實戰 案例1…

Python訓練營打卡Day41-Grad-CAM與Hook函數

知識點回顧回調函數lambda函數hook函數的模塊鉤子和張量鉤子Grad-CAM的示例 作業&#xff1a;理解下今天的代碼即可 在深度學習中&#xff0c;我們經常需要查看或修改模型中間層的輸出或梯度。然而&#xff0c;標準的前向傳播和反向傳播過程通常是一個黑盒&#xff0c;我們很難…

使用VBA宏批量修改Word中表格題注格式

目錄&#x1f4c2; 使用步驟? 方式一&#xff1a;應用已有樣式&#xff08;推薦&#xff09;代碼實現說明? 方式二&#xff1a;手動設置字體格式&#xff08;無需預定義樣式&#xff09;代碼實現參數說明如何運行宏&#xff1f;補充建議總結在撰寫論文、技術文檔或報告時&…

Redis面試精講 Day 27:Redis 7.0/8.0新特性深度解析

【Redis面試精講 Day 27】Redis 7.0/8.0新特性深度解析 在“Redis面試精講”系列的第27天&#xff0c;我們將聚焦Redis最新版本——7.0與8.0的核心新特性。隨著Redis從內存數據庫向云原生、高可用、高性能中間件持續演進&#xff0c;7.0和8.0版本引入了多項顛覆性改進&#xf…

使用自制的NTC測量模塊測試Plecs的熱仿真效果

之前構建的 NTC 溫度測量模型是進行 PLECS 熱仿真的完美起點和核心組成部分。 PLECS 的強大之處在于它能夠進行多域仿真,特別是電-熱聯合仿真。您可以將電路仿真(包括您的 NTC 測量模型)與熱仿真(散熱器、熱容、熱阻等)無縫地結合起來。 電-熱聯合仿真原理 整個仿真閉環…

C語言初學者筆記【動態內存管理】

、 文章目錄一、為什么需要動態內存分配&#xff1f;二、malloc 和 free1. malloc2. free三、calloc 和 realloc1. calloc2. realloc四、常見的動態內存錯誤1. 對 NULL 解引用2. 越界訪問3. 對非動態內存使用 free4. 釋放部分動態內存5. 多次釋放同一塊內存6. 內存泄漏五、動態…

AI模型部署 - 大語言模型(LLM)部署技術與框架

目錄 一、 大語言模型部署的核心挑戰與關鍵技術 二、 主流開源部署框架深度解析 2.1. Ollama:本地部署的極簡主義者 2.2. Hugging Face TGI (Text Generation Inference) 2.3. vLLM:為吞吐量而生 2.4. sglang:面向復雜提示與結構化輸出的革新者 三、 特定硬件與云平臺…

Windows11 GeForce GTX 1060 CUDA+CUDNN+Pytorch 下載及安裝

一、查看顯卡型號信息 系統&#xff1a;Windows11 顯卡&#xff1a;GeForce GTX 1060 型號&#xff1a; &#xff08;1&#xff09;搜索 NVIDIA&#xff0c;選擇 NVIDIA Control Panel&#xff08;2&#xff09;打開 NVIDIA control Panel&#xff0c;打開系統信息&#xff0c;…

在通義靈碼中配置MCP服務

目錄 查找mcp列表 通義靈碼中配置MCP 使用方式 STDIO (Standard Input/Output) 組成部分&#xff1a; SSE (Server-Sent Events) 特點&#xff1a; 主要區別對比 配置方式 配置優先級 個人設置 項目設置 驗證 通過MCP調用高德地圖 查找mcp列表 打開ModelScope - …

網絡中的IO問題(五種常見的IO方式)

什么是高效的IO&#xff1f; 正常情況下&#xff0c;IO等拷貝 高效的IO拷貝&#xff08;即讓IO盡量不等&#xff09; 為什么我們平常玩電腦的時候&#xff0c;感覺不到等待的過程呢&#xff1f; 任何通信場景&#xff0c;IO通信場景&#xff0c;效率一定是有上限的. 花盆里&am…

JAVA核心基礎篇-修飾符

Java 修飾符主要用于定義類、方法或變量&#xff0c;通常放在語句的最前端&#xff0c;可分為訪問修飾符和非訪問修飾符兩類。一、訪問修飾符public&#xff1a;對所有類可見&#xff0c;可用于類、接口、變量和方法。被聲明為 public 的類、方法、構造方法和接口能夠被任何其他…