lua 5.3.5 使用pairs遍历table时, 遍历结果为什么是随机的
目錄
- 1 遍歷結(jié)果是隨機(jī)的
- 2 為什么會是隨機(jī)的
- 2.1 簡單介紹table
- 2.1.1 數(shù)組部分
- 2.1.2 散列表部分
- 2.2 獲取key在散列表中的位置
- 2.2.1 首先介紹幾個宏
- 2.2.2 獲取key在散列表中的位置
- 2.3 key為字符串,獲取key(類型為字符串)在散列表中的位置
- 2.3.1 TString結(jié)構(gòu)體
- 2.3.2 TString.hash的賦值
- 2.4 計算G->seed
- 3 總結(jié)
這里不討論lua中pairs和ipairs的區(qū)別。僅僅從lua源碼的角度,討論使用pairs遍歷table的時候,遍歷結(jié)果為什么是隨機(jī)的。
1 遍歷結(jié)果是隨機(jī)的
首先需要弄清楚,這個“遍歷結(jié)果是隨機(jī)的”是什么意思?
(1) 假設(shè)有 test.lua 文件如下,函數(shù)testPairs() 用來遍歷輸出table s:
function testPairs()s = {a=1,aa=2,b=3,c=4,ab=3,xx=4,ax=5,ay=4,xy=6}for k,v in pairs(s) doprint(k,v)end endtestPairs() print('-----------------------------') testPairs()代碼中執(zhí)行了兩次 testPairs()函數(shù),那么輸出結(jié)果如何呢:
這不是一模一樣嗎,哪里隨機(jī)了?可能是table中元素不夠多,再多塞幾個元素到table中后,發(fā)現(xiàn)結(jié)果都是一樣的。
(2) 換種方式,更改一下"test.lua"中的代碼
function testPairs()s = {a=1,aa=2,b=3,c=4,ab=3,xx=4,ax=5,ay=4,xy=6}for k,v in pairs(s) doprint(k,v)end endtestPairs()然后在命令行窗口中前后執(zhí)行兩次” lua test.lua”,結(jié)果如下:
這樣子做,遍歷結(jié)果的順序才是隨機(jī)的。此處通過表現(xiàn),先簡單下個結(jié)論。
使用pairs遍歷table時:
如果是同一次lua程序運行期間(同一個global_State下),遍歷結(jié)果的順序是不變的(畢竟table中的值都沒有變過);
否則,順序是隨機(jī)的。
2 為什么會是隨機(jī)的
2.1 簡單介紹table
本文僅涉及到table中的遍歷,只討論table中元素的存儲方式,所以不贅述table的相關(guān)操作(插入,刪除節(jié)點等操作)。
除了源碼,本文還參考了codedump的《lua設(shè)計與實現(xiàn)》(版本5.1.4)
table的定義如下:
與遍歷相關(guān)的字段為:
可以知道table是由數(shù)組和散列表兩個部分組成的。
2.1.1 數(shù)組部分
這部分比較簡單,只需知道對于array[i],key = i + 1, value = array[i]。
所以,key-value中的value存儲位置為:
(1) 數(shù)組中,當(dāng)(key為正整數(shù) && 1<= key <= sizearray )
(2) 散列表中,其他值
2.1.2 散列表部分
根據(jù)key的類型和值,求得其對應(yīng)的hash值,然后放到散列表中(當(dāng)然可能會有沖突,處理沖突的方式可進(jìn)一步閱讀函數(shù) luaH_newkey())
2.2 獲取key在散列表中的位置
那么將一個key-value插入到table的散列表中,其對應(yīng)的位置是怎么計算的呢(此處不考慮有沖突的情況)。恰好,lua 源碼中將該功能提取成一個函數(shù):
Static Node *mainposition(const Table *t, const TValue *key)
2.2.1 首先介紹幾個宏
(1) sizenode() 求table的散列表大小
(2) lmod() 取模操作,這里的size其實就是(1)中的sizenode(t)
(3) gnode() 取table散列表中序號為i的元素的地址
(4) hashpow2(t, n) 根據(jù)n的值,對其進(jìn)行l(wèi)mod操作,返回其在散列表中的位置
好的,這里記住hashpow2(t, n)的功能就可以了。
2.2.2 獲取key在散列表中的位置
這里代碼不多,直接上代碼
根據(jù)key的類型,選擇不同的計算方式:
簡單挑選幾個重要類型
(1) LUA_TNUMINT 整型
可以知道,針對整型,其實就是對其值求lmod操作。這說明如果key是整數(shù),其在散列表中的位置是不會變的(在前后兩次調(diào)用lua程序中,table的大小是不變的,array和node的大小也不會變)。
(2)LUA_TNUMFLT 浮點型
這里不展開l_hashfloat() 操作,只需知道,它是用來計算float的hash值即可
通過hashmod(t, n)可以知道,當(dāng)key是浮點數(shù)的時候,其在散列表中的位置是不會變的。
(3) LUA_TSHRSTR 短字符串
這里不展開tsvalue(key),只需知道,它是用來將key從TValue類型轉(zhuǎn)換為TString類型(并不是普通的強(qiáng)制轉(zhuǎn)換,和TValue、GCUion結(jié)構(gòu)有關(guān))。
可以知道,針對短字符串,其實是對其hash值(TString.hash)求lmod操作。TString.hash值的獲取后面會詳細(xì)講。
(4) LUA_TLNGSTR 長字符串
可以知道,針對長字符串,其實是使用函數(shù) luaS_hashlongstr(TSring *)得到的值,并對其值求lmod操作。函數(shù) luaS_hashlongstr()后面會詳細(xì)講解。
根據(jù)以上四個類型可以知道,在散列表的長度不變(array和node長度不變)的情況下,對于一般類型(整數(shù),浮點數(shù),boolean等)的key, 其在散列表中的位置是不會變的。可以推測,如果在test.lua中,這樣設(shè)置table: t = {1,2,3, [100]=100, [200]=200},則其遍歷順序肯定是固定的,不是隨機(jī)的了。
所以這里面需要進(jìn)一步探討的就是短字符串和長字符串了。
2.3 key為字符串,獲取key(類型為字符串)在散列表中的位置
2.3.1 TString結(jié)構(gòu)體
先上TString結(jié)構(gòu),長、短字符串使用的都是這個結(jié)構(gòu)體
只需要注意字段TString.hash,其值在求字符串在散列表中的位置時,起了至關(guān)重要的作用。
2.3.2 TString.hash的賦值
首先介紹一下函數(shù)luaS_hash(), 用來求一個字符串對應(yīng)的hash值(這里暫時把函數(shù)luaS_hash(str, l, seed)的返回值叫做"字符串str的hash值")
其中str指向的是字符串,l是字符串的長度,seed是一個種子值。
通過函數(shù)luaS_hash()可知,只要種子不變,那么字符串對應(yīng)的hash值當(dāng)然也是不變的。
在new TString的時候,Tstring.hash值是在createstrobj()函數(shù)中進(jìn)行賦值的,其值為h。
現(xiàn)在,需要找找是哪里調(diào)用了 createstrobj(),且參數(shù)h是多少?
(1) 新建字符串TString
(2) 短字符串
internshrstr()函數(shù)較長,這里折疊一部分無關(guān)代碼。
(lua源碼中經(jīng)常喜歡用小寫字母的l,和數(shù)字1有點像,這里替換成size,表示字符串的長度)
可以知道,對于短字符串來說,TString.hash = luaS_hash(str, size, g->seed),其hash值和(str, size, G->seed)有關(guān)
(3) 長字符串
可以知道,對于長字符串來說, 初始的TString.hash = G->seed
現(xiàn)在我們回到2.2.2中求長字符串在散列表中的位置:
在函數(shù)createstrobj中可以知道,長字符串字段TString.extra的初始值為0,那么第一次求其散列表位置需要重新更新其TString.hash值,為:
luaS_hash(str, size, ts.hash)
而長字符串的hash字段的初始值也是G->seed。
與此可以知道,不管是長字符串還是短字符串,在求其在散列表中的位置時,都是使用luaS_hash(str, size, G->seed)的值進(jìn)行l(wèi)mod操作后求得。
那么在兩次啟動lua程序過程中,字符串是沒有變化的(也就是str, size都是不變的),要想遍歷結(jié)果的順序是隨機(jī)的(對應(yīng)散列表的位置是隨機(jī)的),那么只能是說G->seed是隨機(jī)的了。結(jié)果是這樣子嗎?
2.4 計算G->seed
直接上代碼
G->seed是在函數(shù)lua_newstate()中賦值的
(1) 四個addbuff()
makeseed()函數(shù)中連用了四個addbuff,其實就是將四個變量依次copy到char buff [4*sizeof(size_t)]中。
(2) Luai_makeseed()
這個比較簡單,其實就是調(diào)用C庫中的time()函數(shù),執(zhí)行time(NULL)得到的是當(dāng)前的時間(unix時間戳,單位為秒),
這個值在每次啟動lua程序的時候,是不同的。
(3) luaS_hash(buff, p, h)
這個函數(shù)上面提到過。這里把當(dāng)前Unix時間戳當(dāng)做是seed,把buf字符串傳入,得到一個hash值。這個值就賦給當(dāng)前G->seed
所以撇開buff[]字符串的值的隨機(jī)性不講,光是unix時間戳作為h傳入luaS_hash(buff, p, h)中,就知道,G->seed的值是隨機(jī)的。
這樣,再回到2.3.2 中計算字符串的TSTring.hash值(luaS_hash(str, size, G->seed))。
由此可以得出,先后兩次執(zhí)行l(wèi)ua程序,G->seed是隨機(jī)的,那么TString.hash當(dāng)然也是隨機(jī)的,也就導(dǎo)致TString作為key時插入Table中,其對應(yīng)的散列表的位置,也是隨機(jī)的。
3 總結(jié)
其實篇幅這么大,主要是回顧一下table相關(guān)的東西,解釋了前后兩次執(zhí)行l(wèi)ua程序,使用pairs遍歷相同的table時,為什么遍歷結(jié)果的順序是隨機(jī)的。
原因: 一言以蔽之,global_state.seed是一個和時間戳相關(guān)的隨機(jī)值,這個值會影響字符串的TString.hash字段,當(dāng)TString*作為key插入table中時,這個字段會影響其在散列表中的位置。
總結(jié)
以上是生活随笔為你收集整理的lua 5.3.5 使用pairs遍历table时, 遍历结果为什么是随机的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue h5 腾讯地图路线规划
- 下一篇: HTML5不支持createtouch,