内容简介:这一节介绍Lua唯一的数据结构table充分体现了Lua语言的特点,用最简练的语法表达丰富的信息,但也增加了用户的理解成本。table包含本文展示的代码来自llamavm,并非Lua源码,C++版本的实现比较容易理解。
这一节介绍 Lua 唯一的数据结构 table ,相对于大部分语言提供 数组和字典 两种类型,Lua将其合二为一,颇为精巧的实现了table。
table充分体现了Lua语言的特点,用最简练的语法表达丰富的信息,但也增加了用户的理解成本。table包含 数组和哈希 两部分功能,所以实现起来颇为复杂。
本文展示的代码来自llamavm,并非Lua源码,C++版本的实现比较容易理解。
实现部分包括:
-
数据存储
-
获取key值
-
修改key值
-
自动扩容
-
计算数组长度
-
遍历table
示例代码:
tb = { 10, 'num', {30}; ['k1']='v1', k2=20 } print("输出数组部分") for i=1,#tb do print(tb[i]) end print("输出所有元素") for k,v in pairs(tb) do print(k .. " = " .. tostring(v)) end
执行结果:
输出数组部分 10 num table: 00396F20 输出所有元素 1 = 10 2 = num 3 = table: 00396F20 k1 = v1 k2 = 20
Part1 数据存储
若将数组下标索引(1到n)作为整数key,用一个 哈希表 就能实现table。
key可以为 nil 之外的任意类型,比如 整数key、字符串key,布尔key ,甚至可以用 函数、table 作为key。
在Lua5.0之前,table内部用一个哈希表实现,5.0版本后拆分为数组和哈希两个部分。
两种实现的区别
(1)一个哈希表实现, 所有key 都存储在哈希表内
Node nodes[]; nodes[1] = 10 nodes[2] = 'num' nodes[3] = table: 00396F20 nodes['k1'] = 'v1' nodes['k2'] = 20
(2)数组加哈希表实现, 部分整数key 存放在数组,其余key存放在哈希表
Object arrays[]; Node nodes[]; arrays[1] = 10 arrays[2] = 'num' arrays[3] = table: 00396F20 nodes['k1'] = 'v1' nodes['k2'] = 20
将部分整数key放在数组部分,显然是为了性能考虑,这里引用一段 官方说明 :
混合机制有两个优点:
第一:访问整型key的操作会变得更快了,因为不再需要哈希。
第二:更重要的是,数组部分只占原来哈希部分的一半大小,因为哈希部分需要同时存储key和value,而数组部分的key已经隐含在下标了。
结果是,如果一个table是作为数组使用的,它的表现就像数组一样,只要它的整型key是密集分布的。而且,哈希部分没有内存或者时间的代价,因为作为数组使用时,哈希部分不存在。
反过来说,如果table是作为记录使用而非数组,那么数组部分就是空的。这些节省下来的内存是重要的,因为对于Lua程序来说,创建大量小table是很常见的(比如用table来表示object)。
Lua的table也能优雅的处理稀疏数组:语句a={[1000000000]=1}在哈希部分创建了一个键值对,而非一个10亿元素的数组。
数组部分的实现比较简单,主要介绍 哈希部分 的实现。
在《 字符串实现 》一节里介绍了通过哈希表实现字符串池,采用 链地址法 解决hash冲突。在table里采用 开放地址法 解决hash冲突,table类型定义如下:
struct Table { struct Node { TObject key; TObject value; Node* next; }; /*数组部分*/ TObject* arrayData; int arraySize; /*哈希部分*/ Node* hashData; int hashSize; int last_free; };
-
数组部分存放在arrayData,每个元素为一个 Object
-
哈希部分存放在hashData,每个元素为一个 Node
-
Node包括key、value,以及指向 下一个冲突结点 的指针
-
last_free表示最后一个 空闲结点 的位置,避免遍历查找
举例说明
(1)有3个结点,key分别为 aa、bb、cc
{'aa', 100}
{'bb', 200}
{'cc', 300}
其中'aa'和'cc'的hash值都为 401 ,产生冲突( 哈希算法比较差 ),'bb'的哈希码为 402
hashcode('aa') = 401
hashcode('bb') = 402
hashcode('cc') = 401
(2)添加这3个结点到table,Node数组大小为 4 ,根据key的 hash值 计算数组位置
pos_aa = hashcode('aa') % 4 = 1
pos_bb = hashcode('bb') % 4 = 2
pos_cc = hashcode('cc') % 4 = 1
(3)添加'aa',添加到位置1
hashData[1] = Node{key='aa', value=100, next=NULL}
(4)添加'bb',添加到位置2
hashData[2] = Node{key='bb', value=200, next=NULL}
(5)添加'cc', 位置1已经被'aa'占据 ,挑选一个空闲位置,last_free=3,添加到位置3
hashData[3] = Node{key='cc', value=300, next=NULL}
3个结点添加完毕,Node数组为:
hashData[0] = Node {}
hashData[1] = Node{key=' aa ', value=100, next=NULL}
hashData[2] = Node{key=' bb ', value=200, next=NULL}
hashData[3] = Node{key=' cc ', value=300, next=NULL}
(6)由于'aa'和'cc' 冲突 ,'cc'不在其 主位置 上( 应该在位置1,实际在位置3 ),需要将'aa'和'cc'串联起来,构成 冲突链
hashData[0] = Node {}
hashData[1] = Node{key='aa', value=100, next=&hashData[3] }
hashData[2] = Node{key='bb', value=200, next=NULL}
hashData[3] = Node{key='cc', value=300, next=NULL}
查找'cc'时,先查找位置1,找到'aa',再根据'aa'的 next 指针找到'cc'。
table结构图:
Part2 获取key值
根据前面的分析,大概了解key的查询方法,流程如下:
-
先确定key 是否在数组 部分,比如数组部分长度为4,若key为 1~4 会在数组部分查找,其余key都在 哈希 部分查找
-
若在数组部分,直接根据 索引 查找。数组部分的扩容,在rehash部分介绍
-
若在哈希部分,先根据key的 hash值 计算其位置,再通过Node的 next 指针遍历冲突链,找到对应key。
关键点在于 如何计算key的hash值 ,key可以为多种类型,需要针对每种类型计算其哈希值。
(1)字符串
字符串对象本身携带hash值,可以直接使用。
(2)数值
整数值可以直接用作hash值。对于浮点数,考虑到小数部分的不同也会影响hash值,可以将小数累加到整数部分
n = 123.456789
hashcode(n) = (n - (int)n) * 1000000 + n = 456912
(3)指针类型
指针本身就是数值,可以直接用作hash值
hashcode(key) = ( long )ptr
总体来说,应尽量利用数据的 每个字节 计算hash值,以达到hash散列的效果。
Part3 修改key值
要修改key的值,需要先找到key所在的位置,这一点和 “获取key值” 原理相同。若找到对应的key,直接修改其value值。
若 没找到 key,添加到 哈希 部分,流程如下:
当 主位置被占用 ,且有 空闲结点 的时候,需要调整结点的位置,流程如下:
这里逻辑有些复杂,举例讲解:
(1)假定Node数组长度为 4 ,先添加key 'aa',通过hash值计算其主位置应该为 1
Node[1] = 'aa'
(2)添加key 'bb',恰巧其主位置 也为1 ,由于'aa'在其 正确 位置上,分配最后一个 空闲结点 给'bb',且建立 冲突链 ,'aa'指向'bb'
Node[1] = 'aa', next->[3]'bb'
Node[ 3 ] = 'bb'
(3)添加key 'cc',其主位置为 3 ,但'bb' 已经占用 了位置3,由于'bb'的 实际 主位置应该为 1 ,所以需要将 'bb'移走,归还给'cc'
-
通过冲突链,获取'bb'的 前置 结点'aa'
-
分配空闲位置给'bb',last_free=2
-
'aa'指向'bb'的新位置
-
'cc'存放在其主位置
Node[1] = 'aa', next->[2]'bb'
Node[ 2 ] = 'bb'
Node[ 3 ] = 'cc'
'bb'从位置3挪动到位置2,'cc'使用位置3,在挪动前后,'aa'始终指向'bb'。
关于table的数据存储、读写key的内部实现介绍到这里,下节介绍其余部分的实现, 欢迎留言 发表您的建议!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP and MySQL Web Development
Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99
Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!