您的位置:首页 > 编程语言 > Lua

《Lua 5.0的实现》第四章 - 表

2013-11-25 20:21 106 查看
    Table是Lua主要(实际上是唯一)的数据结构。Table在语言中和实现上都起到了关键的作用。Lua语言受益于我们精心设计的table,因为table用在几个内部的任务上,而无需担心性能问题。这有助于保持Lua设计的小巧。但是,由于缺乏其他数据结构,就对用table实现的效率提出了更高的要求。

    Table在Lua中是相关联的数组,它的索引可以是任何值(除了nil),也可以将任何类型的值保持到table中。此外,当数据添加到table的时候(通过把一个值分配给一个尚未存在的域),table的大小就会增长,从这方面来说table是动态的。相应的,当数据从table中移除的时候,table的大小就会减小。

    和其他脚本语言不同,Lua没有数组。table使用整数的key表示数组。把table当做数组来使用能给语言带来好处。主要的理由很简单:Lua不需要为table和数组提供两套不同的运算符。此外,程序员不需要在两者中做出选择。稀疏数组的实现在Lua中是不重要的。比如,在Perl中,当如果你运行$a[1000000000] = 1这样的程序时,就会触发perl去创建十亿个元素,导致内存溢出。在等价的Lua程序中,a={[1000000000]=1}创建的table只有一个项。



    到Lua 4.0为止,table都被严格的按照哈希表来实现:所有键值都明确的存储在table内。Lua 5.0引入了新的算法优化作为数组使用的table:它优化了以整数为key的键值,不以键值对的方式存储,而是把值存储到实际的数组里。更精确的说,在Lua 5.0中,table是一个混合的数据结构:它由哈希部分和数组部分组成。图2表示为给一个table赋上了几个键值对:"x"
-> 9.3,1 -> 100,  2 -> 200,3 -> 300。注意图的右边是数组:它没有存储整数的key。这个划分是很底层的实现,对table的访问,甚至对虚拟机来说都是透明的。table根据内容自动且动态地将它的内容分成两部分:数组部分将key从1到有限的n之间的数组保存起来;key不是整数或者超出了数组大小的键值保存到哈希表中。

    当table要增加的时候,Lua重新计算哈希部分和数组部分的大小。此时也许两部分都是空的。计算数组大小的最大值n过程为:数组中的项至少有一半在1到n之间(为了避免稀疏数组时空间的浪费),至少有一项在n/2 + 1 到n之间(防止n/2就已经足够的情况下,偏去申请n大小的数组)。在计算出新大小以后,Lua创建了这个新的数组,然后将原先数组中的内容重新插入到新的数组。例如有一个空的table,哈希和数组部分的大小都是0。如果我们计算a[1]
= v,table要增大不容纳这个新key。Lua会为新数组选择n = 1作为它的大小(只有一个项 1 -> v)。哈希部分还是空的。

    混合方案有两大优势:第一,由于没有哈希,访问整数key的速度更快。第二,更重要的是数组部分比哈希部分节省了一半的存储空间,因为key在数组中是隐式存储的,在哈希表中是显式存储的。因此,只要table中的整数key比较密集,table就会当成数组使用。此外还节省了哈希表的时间和空间,甚至哈希表都不用存在。相反的是:如果把table当做关联数组使用,而没有当成数组使用,此时数组部分应该是空的。这里的内存节省是很重要的因为在Lua编程中,创建很多小table是很常见的事情,例如table当成对象使用的时候。

    哈希部分使用了混合的发散列表链和Brent's variation。哈希表有一个定式:如果一个元素没有出现在它最优选择的位置上(这个初始的(最优)位置是它的哈希值决定的),那么就说明有一个冲突的元素占据了它的最优位置。换句话说,冲突只发生在两个元素拥有同样的最优位置时(例如,两个同样的哈希值在表一样大的时候,就会冲突)。不会出现第二级冲突(译者注:最佳位置被别的元素占据了,它的第二选择位肯定是它自己的,而不会被其他元素占据)。正因为如此,这些哈希表的负载因子可以达到100%,而不会出现性能下降的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: