lua设计与实现_Lua设计与实现--Table篇

384fda8d7fbb044507ace38a3810225c.png

本篇文章是Lua设计与实现专栏的第四篇,主要结合了《Lua设计与实现》书中的第4章(表),以及lua5.3源码进行一些总结,由于原书中主要是基于lua5.1进行书写的,所以可能会有跟书中列举代码不一致的地方,不过大体上是保持一致的。

Table的设计哲学

table应该算是lua最灵魂的一个结构了。它有以下特点:

  • 容器功能:与其他语言相似,lua也内置了容器功能,也就是table。而与其他语言不同的是,lua内置容器只有table。正因为如此,为了适配不同的应用需求,table的内部结构也比较考究,分为了数组和哈希表两个部分,根据不同需求来决定使用哪个部分。
  • 面向对象功能:与其他语言不同的时,lua并没有把面向对象的功能以语法的形式包装给开发者。而是保留了这样一种能力,待开发者去实现自己的面向对象。而这一保留的能力,也是封装在table里的:table里可以组合一个metatable,这个metatable本身也是一个table,它的字段用来描述原table的行为。

lua是一个短小精悍的语言,之所以把这么多功能全都聚合到table结构中,我的理解是,它不想让开发者去关心过多的类型,能够真正做到table在手,天下我有。同时,虽然table要兼具这么多的功能,但是lua在实现它的时候,其实是非常克制的,做到了真正的灵活可扩展,开发者可以在table提供的基础功能下扩展出非常多样性的结构和类型。

Table的数据结构

table的数据结构也是围绕着设计来的,主要分为实现容器的数据结构和metatable,如下所示:

8e9dc39b5dd9f36cc646e71889fc0925.png

来看看各个成员的含义:

  • CommonHeader:垃圾回收通用结构,详情参考本专栏数据结构篇。
  • flags:用于cache该表中实现了哪些元方法。
  • lsizenode:哈希表大小取log2(哈希表大小只会为2的次幂)
  • sizearray:数组大小(数组大小只会为2的次幂)
  • array:数组头指针
  • node:哈希表头指针
  • lastfree:哈希表可用尾指针,可用的节点只会小于该lastfree节点。
  • metatable:元表
  • gclist:GC的链表,用于垃圾回收。

大致的结构图如下(因为5.3实现略微有所不同,把5.1书上的图稍微改了一下):

bbbf73b58633006b33a954621379ab65.png

Table的重要操作

1.查询操作

d0fc372872751a08b7f6d733728517f4.png

查询key是否存在,分为了int和非int类型。如果key是int类型并且小于sizearray,那么直接返回对应slot。否则走hash表查询该key对应的slot。

2.新增元素: 新增元素的核心是新增key,该操作由luaH_newkey来实现。该函数的主要步骤如下:

  • 如果freepos为NULL(没有可用空间了),调用rehash来扩容。rehash的具体细节将在后文详解。

f565ec496e46ecc73dfda1a5bf53e599.png
  • 找到newkey的mainposition,看是否可用,如果可用直接使用;
  • 如果newkey的mainposition已经被占用了,那么分为两种情况:
  • (1)占用的节点和newkey的哈希值相同,那么直接插入到该mainposition的next。

0ef9916337b36e7f1c17f9b9f01d0cf9.png
  • (2)如果占用节点的hash值与newkey不同,说明该节点是被“挤”到该位置来的,那么把该节点挪到freepos去,然后让newkey入住其mainposition。

e24596cf918110c6028ff0192d217b59.png

3.rehash操作

87290d75e2b6c93474c8c22d41431e8f.png

由于lua的表是由数组和哈希表两部分组成,因此rehash要做的事情并不只是简单字面上的重新hash,它主要有两部分工作:

  • 根据已有元素和新加元素重新计算数组大小和哈希表大小。这一部分的计算是比较有意思的。首先,各个局部变量的含义如下:
  • nums数组:它的第i个位置存储的是key在2^(i-1)~2^i区间内的数量。
  • asize:最终数组的大小(一定为2的次幂)。
  • na:最终归入数组部分的key的个数。
  • totaluse:总共的key个数。

rehash函数先后调用了几个相关函数来更新以上几个字段: - numusearray:遍历当前的array部分,按其中key的分布来更新nums数组。同时返回na。将totaluse加上na。 - numusehash:遍历当前的hash表部分,如果其中的key为整数,na++并且更新nums数组,对于每个遍历的元素,totaluse++。 - countint:将newkey传进去,如果是整型的,那么na++。 - computesizes:计算optimal的array部分大小。这个函数根据整型key在2^(i-1)~2^i之间的填充率,来决定最终的array大小。一旦遇到某个子区间的填充率小于1/2,那么后续的整型key都存储到hash表中去,这一步是为了防止数组过于稀疏而浪费内存。函数将返回asize以及na。 - 调用luaH_resize,根据上一步计算出的最终数组和哈希表大小,进行resize操作。当然,如果hash表的尺寸有变化,会对原来哈希表中的元素进行真正的rehash。

4.迭代操作: 在使用测主要是ipairs和pairs两个函数。这两个函数都会在vm内部临时创建出两个变量state和index,用于对lua表进行迭代访问,每次访问的时候,会调用luaH_next函数:

dc100e0f894a58aaf17ea6d961cf7e7f.png

该函数首先会根据findindex函数,找出迭代器对应lua表的哪个部分,是数组部分还是hash表部分。如果是数组部分,index会小于sizearray,否则会大于sizearray。注意该函数中的两个循环,只会进一个,取决于index是否小于sizearray。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值