lua 5.3.5 使用pairs遍历table时, 遍历结果为什么是随机的
目录
- 1 遍历结果是随机的
- 2 为什么会是随机的
- 2.1 简单介绍table
- 2.1.1 数组部分
- 2.1.2 散列表部分
- 2.2 获取key在散列表中的位置
- 2.2.1 首先介绍几个宏
- 2.2.2 获取key在散列表中的位置
- 2.3 key为字符串,获取key(类型为字符串)在散列表中的位置
- 2.3.1 TString结构体
- 2.3.2 TString.hash的赋值
- 2.4 计算G->seed
- 3 总结
这里不讨论lua中pairs和ipairs的区别。仅仅从lua源码的角度,讨论使用pairs遍历table的时候,遍历结果为什么是随机的。
1 遍历结果是随机的
首先需要弄清楚,这个“遍历结果是随机的”是什么意思?
(1) 假设有 test.lua 文件如下,函数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()
代码中执行了两次 testPairs()函数,那么输出结果如何呢:
这不是一模一样吗,哪里随机了?可能是table中元素不够多,再多塞几个元素到table中后,发现结果都是一样的。
(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()
然后在命令行窗口中前后执行两次” lua test.lua”,结果如下:
这样子做,遍历结果的顺序才是随机的。此处通过表现,先简单下个结论。
使用pairs遍历table时:
如果是同一次lua程序运行期间(同一个global_State下),遍历结果的顺序是不变的(毕竟table中的值都没有变过);
否则,顺序是随机的。
2 为什么会是随机的
2.1 简单介绍table
本文仅涉及到table中的遍历,只讨论table中元素的存储方式,所以不赘述table的相关操作(插入,删除节点等操作)。
除了源码,本文还参考了codedump的《lua设计与实现》(版本5.1.4)
table的定义如下:
与遍历相关的字段为:
可以知道table是由数组和散列表两个部分组成的。
2.1.1 数组部分
这部分比较简单,只需知道对于array[i],key = i + 1, value = array[i]。
所以,key-value中的value存储位置为:
(1) 数组中,当(key为正整数 && 1<= key <= sizearray )
(2) 散列表中,其他值
2.1.2 散列表部分
根据key的类型和值,求得其对应的hash值,然后放到散列表中(当然可能会有冲突,处理冲突的方式可进一步阅读函数 luaH_newkey())
2.2 获取key在散列表中的位置
那么将一个key-value插入到table的散列表中,其对应的位置是怎么计算的呢(此处不考虑有冲突的情况)。恰好,lua 源码中将该功能提取成一个函数:
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) 根据n的值,对其进行lmod操作,返回其在散列表中的位置
好的,这里记住hashpow2(t, n)的功能就可以了。
2.2.2 获取key在散列表中的位置
这里代码不多,直接上代码
根据key的类型,选择不同的计算方式:
简单挑选几个重要类型
(1) LUA_TNUMINT 整型
可以知道,针对整型,其实就是对其值求lmod操作。这说明如果key是整数,其在散列表中的位置是不会变的(在前后两次调用lua程序中,table的大小是不变的,array和node的大小也不会变)。
(2)LUA_TNUMFLT 浮点型
这里不展开l_hashfloat() 操作,只需知道,它是用来计算float的hash值即可
通过hashmod(t, n)可以知道,当key是浮点数的时候,其在散列表中的位置是不会变的。
(3) LUA_TSHRSTR 短字符串
这里不展开tsvalue(key),只需知道,它是用来将key从TValue类型转换为TString类型(并不是普通的强制转换,和TValue、GCUion结构有关)。
可以知道,针对短字符串,其实是对其hash值(TString.hash)求lmod操作。TString.hash值的获取后面会详细讲。
(4) LUA_TLNGSTR 长字符串
可以知道,针对长字符串,其实是使用函数 luaS_hashlongstr(TSring *)得到的值,并对其值求lmod操作。函数 luaS_hashlongstr()后面会详细讲解。
根据以上四个类型可以知道,在散列表的长度不变(array和node长度不变)的情况下,对于一般类型(整数,浮点数,boolean等)的key, 其在散列表中的位置是不会变的。可以推测,如果在test.lua中,这样设置table: t = {1,2,3, [100]=100, [200]=200},则其遍历顺序肯定是固定的,不是随机的了。
所以这里面需要进一步探讨的就是短字符串和长字符串了。
2.3 key为字符串,获取key(类型为字符串)在散列表中的位置
2.3.1 TString结构体
先上TString结构,长、短字符串使用的都是这个结构体
只需要注意字段TString.hash,其值在求字符串在散列表中的位置时,起了至关重要的作用。
2.3.2 TString.hash的赋值
首先介绍一下函数luaS_hash(), 用来求一个字符串对应的hash值(这里暂时把函数luaS_hash(str, l, seed)的返回值叫做"字符串str的hash值")
其中str指向的是字符串,l是字符串的长度,seed是一个种子值。
通过函数luaS_hash()可知,只要种子不变,那么字符串对应的hash值当然也是不变的。
在new TString的时候,Tstring.hash值是在createstrobj()函数中进行赋值的,其值为h。
现在,需要找找是哪里调用了 createstrobj(),且参数h是多少?
(1) 新建字符串TString
(2) 短字符串
internshrstr()函数较长,这里折叠一部分无关代码。
(lua源码中经常喜欢用小写字母的l,和数字1有点像,这里替换成size,表示字符串的长度)
可以知道,对于短字符串来说,TString.hash = luaS_hash(str, size, g->seed),其hash值和(str, size, G->seed)有关
(3) 长字符串
可以知道,对于长字符串来说, 初始的TString.hash = G->seed
现在我们回到2.2.2中求长字符串在散列表中的位置:
在函数createstrobj中可以知道,长字符串字段TString.extra的初始值为0,那么第一次求其散列表位置需要重新更新其TString.hash值,为:
luaS_hash(str, size, ts.hash)
而长字符串的hash字段的初始值也是G->seed。
与此可以知道,不管是长字符串还是短字符串,在求其在散列表中的位置时,都是使用luaS_hash(str, size, G->seed)的值进行lmod操作后求得。
那么在两次启动lua程序过程中,字符串是没有变化的(也就是str, size都是不变的),要想遍历结果的顺序是随机的(对应散列表的位置是随机的),那么只能是说G->seed是随机的了。结果是这样子吗?
2.4 计算G->seed
直接上代码
G->seed是在函数lua_newstate()中赋值的
(1) 四个addbuff()
makeseed()函数中连用了四个addbuff,其实就是将四个变量依次copy到char buff [4*sizeof(size_t)]中。
(2) Luai_makeseed()
这个比较简单,其实就是调用C库中的time()函数,执行time(NULL)得到的是当前的时间(unix时间戳,单位为秒),
这个值在每次启动lua程序的时候,是不同的。
(3) luaS_hash(buff, p, h)
这个函数上面提到过。这里把当前Unix时间戳当做是seed,把buf字符串传入,得到一个hash值。这个值就赋给当前G->seed
所以撇开buff[]字符串的值的随机性不讲,光是unix时间戳作为h传入luaS_hash(buff, p, h)中,就知道,G->seed的值是随机的。
这样,再回到2.3.2 中计算字符串的TSTring.hash值(luaS_hash(str, size, G->seed))。
由此可以得出,先后两次执行lua程序,G->seed是随机的,那么TString.hash当然也是随机的,也就导致TString作为key时插入Table中,其对应的散列表的位置,也是随机的。
3 总结
其实篇幅这么大,主要是回顾一下table相关的东西,解释了前后两次执行lua程序,使用pairs遍历相同的table时,为什么遍历结果的顺序是随机的。
原因: 一言以蔽之,global_state.seed是一个和时间戳相关的随机值,这个值会影响字符串的TString.hash字段,当TString*作为key插入table中时,这个字段会影响其在散列表中的位置。
lua 5.3.5 使用pairs遍历table时, 遍历结果为什么是随机的相关推荐
- Lua中,泛型for循环遍历table时,ipairs和pairs的区别
根据table型变量key是否为连续数字,如果是则称为数组型table,如果不是则称为非数组型table. 事实胜于雄辩,接下来通过实验来区分两组迭代器的区别. 首先给出pairs和ipairs在数组 ...
- html里table的遍历,jQuery遍历table
1. $("table").find("tr").each(function(){ $(this).find("td").each(func ...
- lua pairs顺序遍历 table(key必须为连续数值)
Lua常用的4中遍历方式 for key, value in pairs(tbtest) do XXX end 这样的遍历顺序并非是tbtest中table的排列顺序,而是根据tbtest中key的h ...
- Lua 学习笔记:C API 遍历 Table
前情提要 Lua 通过一个虚拟栈与 C 的交互,正数索引自底向上取值,负数索引自顶向下取值. Lua 中的 Table(表)结构可以使用任何数据作为 key 进行取值.使用 C API 访问 Tabl ...
- golang gopher-lua 遍历table元素
前言 gopher-lua中也有c++中的lua.next函数,不过目前我没有查到gopher-lua的next函数用法,因此这里用的是func (ls *LState) ForEach(tb *LT ...
- Lua的函数参数为table时奇特现象
前言 今天在工作中使用lua编写代码时发生了一个有趣的现象,特此记录一下. 问题再现 当lua的函数为table时会发生什么情况,话不多说直接上代码: local tb = {1, 2, 3}func ...
- html里table的遍历,js遍历table中的tr
js遍历table中的tr function tt(){ var table1=document.getElementById('table1'); //节点只支持getElementsByTagNa ...
- JavaScript遍历table
JavaScript遍历table 1.说明 遍历表格中的某行某列,并打印其值 2.实现源码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...
- jQuery遍历table中的tr td并获取td中的值
jQuery遍历table中的tr td并获取td中的值 $(function(){$("#tableId tr").find("td").each(funct ...
最新文章
- Django在Win7下安装与创建项目hello word示例
- ErWin简单使用说明
- 相似度算法(http://blog.sina.com.cn/s/blog_62b83291010127bf.html)
- 小程序 国际化_在国际化您的应用程序时忘记的一件事
- Codeforces Round #498 (Div. 3) - 赛后补题
- IOS 获取系统通讯录中的联系人信息
- 红米k30pro工程测试代码_红米K30 PRO代号曝光,确定推出双版本,更强拍照对标荣耀30...
- Android开发的第一天
- 音乐播放器之QQ音乐最新api,亲测可用
- 怎么样学习Java?
- ArcGIS基础:合并表格(追加、合并工具)
- java 处理表情字符_使用轻量级工具emoji-java处理emoji表情字符
- 朋友圈集赞万能截图生成器威信小程序源码下载
- [比赛记录] 主流机器学习模型模板代码+经验分享[xgb, lgb, Keras, LR]
- 上海大学计算机考研改408,上海大学改考408!
- 2021计算机专业复试总结2
- 2020-10-1 交换机通过CRT保存配置-telnet
- 阿里云服务器新用户优惠
- jquery绿色版dreamweaver提示
- 儿童睡眠慢波的起源、同步和传播