Table内部实现2
这一节介绍Lua唯一的数据结构table,相对于大部分语言提供数组和字典两种类型,Lua将其合二为一,颇为精巧的实现了table。
table充分体现了Lua语言的特点,用最简练的语法表达丰富的信息,但也增加了用户的理解成本。table包含数组和哈希两部分功能,所以实现起来颇为复杂。
本文展示的代码来自llamavm,并非Lua源码,C++版本的实现比较容易理解。
实现部分包括:
数据存储
获取key值
修改key值
自动扩容
计算数组长度
遍历table
示例代码:
执行结果:
Part1 数据存储
若将数组下标索引(1到n)作为整数key,用一个哈希表就能实现table。
key可以为nil之外的任意类型,比如整数key、字符串key,布尔key,甚至可以用函数、table作为key。
在Lua5.0之前,table内部用一个哈希表实现,5.0版本后拆分为数组和哈希两个部分。
两种实现的区别
(1)一个哈希表实现,所有key都存储在哈希表内
(2)数组加哈希表实现,部分整数key存放在数组,其余key存放在哈希表
将部分整数key放在数组部分,显然是为了性能考虑,这里引用一段官方说明:
混合机制有两个优点:
第一:访问整型key的操作会变得更快了,因为不再需要哈希。
第二:更重要的是,数组部分只占原来哈希部分的一半大小,因为哈希部分需要同时存储key和value,而数组部分的key已经隐含在下标了。
结果是,如果一个table是作为数组使用的,它的表现就像数组一样,只要它的整型key是密集分布的。而且,哈希部分没有内存或者时间的代价,因为作为数组使用时,哈希部分不存在。
反过来说,如果table是作为记录使用而非数组,那么数组部分就是空的。这些节省下来的内存是重要的,因为对于Lua程序来说,创建大量小table是很常见的(比如用table来表示object)。
Lua的table也能优雅的处理稀疏数组:语句a={[1000000000]=1}在哈希部分创建了一个键值对,而非一个10亿元素的数组。
数组部分的实现比较简单,主要介绍哈希部分的实现。
在《字符串实现》一节里介绍了通过哈希表实现字符串池,采用链地址法解决hash冲突。在table里采用开放地址法解决hash冲突,table类型定义如下:
数组部分存放在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
(4)添加'bb',添加到位置2
hashData[2] = Node
(5)添加'cc',位置1已经被'aa'占据,挑选一个空闲位置,last_free=3,添加到位置3
hashData[3] = Node
3个结点添加完毕,Node数组为:
hashData[0] = Node {}
hashData[1] = Node
hashData[2] = Node
hashData[3] = Node
(6)由于'aa'和'cc'冲突,'cc'不在其主位置上(应该在位置1,实际在位置3),需要将'aa'和'cc'串联起来,构成冲突链
hashData[0] = Node {}
hashData[1] = Node
hashData[2] = Node
hashData[3] = Node
查找'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内部实现2的更多相关文章
- (转)hashmap hashtable 的区别 Hash table 内部的数据结构
转自:http://www.cnblogs.com/carbs/archive/2012/07/04/2576995.html Hashtable 和 HashMap 做为 Map 的基本特性 两者都 ...
- hive内部表、外部表、分区表、视图
1.Table 内部表 1).与数据库中的Table在概念上是类似的 2).每一个Table在Hive中都有一个相应的目录存储数据 3).所有的Table数据(不包括 External Table) ...
- html中 table的结构 彻底搞清 caption th thead等
正因为有太多 随意 称呼的 教法, 所以 感到很困惑, 如, 很多人把th叫标题. 那人家 caption怎么想, th只是一个跟td一样的角色, 只是对他进行加粗 加黑了而已, 用于某些单元格的内容 ...
- Java finally语句到底是在return之前还是之后执行(JVM字节码分析及内部体系结构)?
之前看了一篇关于"Java finally语句到底是在return之前还是之后执行?"这样的博客,看到兴致处,突然博客里的一个测试用例让我产生了疑惑. 测试用例如下: public ...
- hadoop笔记之Hive的数据存储(内部表)
Hive的数据存储(内部表) Hive的数据存储(内部表) 基于HDFS 可使用hadoop给我们提供的web管理工具查看数据.打开管理工具localhost:9000–>Utilities下的 ...
- data.table 中的动态作用域
data.table 中最常用的语法就是 data[i, j, by],其中 i.j 和 by 都是在动态作用域中被计算的.换句话说,我们不仅可以直接使用列,也可以提前定义诸如 .N ..I 和 .S ...
- jquery table 发送两次请求 解惑
版本1.10 以下链接为一个较低版本解决方案: http://blog.csdn.net/anmo/article/details/17083125 而我的情况有点作, 情况描述: 1,一个页面两个t ...
- 使用element-ui的table组件时,渲染为html格式
背景 今天在做vue的项目时,使用到 element-ui 的 table 组件,使用富文本编辑器进行新增操作后,发现 html格式 并没有被识别 原因 在 element-ui 中,table组件默 ...
- openresty开发系列19--lua的table操作
openresty开发系列19--lua的table操作 Lua中table内部实际采用哈希表和数组分别保存键值对.普通值:下标从1开始 不推荐混合使用这两种赋值方式. local color={fi ...
随机推荐
- unity3d学习笔记(一) 第一人称视角实现和倒计时实现
unity3d学习笔记(一) 第一人称视角实现和倒计时实现 1. 第一人称视角 (1)让mainCamera和player(视角对象)同步在一起 因为我们的player是生成的,所以不能把mainCa ...
- windows安装cnpm步骤
1.首先前往nodejs官网下载nodejs 2.安装nodejs 3.打开cmd,输入npm -v,检查npm是否安装成功.成功返回的话返回输出版本号 4.安装cnpm,输入npm install ...
- 使用部分函数时并未include其所在头文件,但是能编译成功且能运行,为什么?
最近在看APUE,试了上面的一些例子,其中有个例子是使用getpid函数获取进程id,但是在我写demo时,并未引入其所在的头文件unistd.h,结果也能编译成功,也能运行,于是就琢磨下为啥. En ...
- [noip2002] 产生数
题目描述 给出一个整数 n (n<1030)和 k 个变换规则 (k < 15) . 规则: 一位数可变换成另一个一位数: 规则的右部不能为零. 例如:n = 234 .有规则( k=2 ...
- CODING DevOps 微服务项目实战系列第一课,明天等你
CODING DevOps 微服务项目实战系列第一课<DevOps 微服务项目实战:DevOps 初体验>将由 CODING DevOps 开发工程师 王宽老师 向大家介绍 DevOps ...
- vue watch 和 computed 区别与使用
目录 computed 和 watch 的说明 与 区别 computed 计算属性说明: watch 监听属性说明: watch 和 computed 的区别是: 使用 参考官方文档 compute ...
- ssh连接:Socket error Event: 32 Error: 10053.
今天在使用xshell连接刚装的linux系统的时候,发现无法建立连接,会报如下错误: Connecting to 192.168.21x.x:22...Connection established. ...
- Qt 实现 异形 窗体&按钮
//关键部分代码如下//设置异形窗体 //setWindowOpacity(0.5);//设置窗体透明度 0完全透明,1完全不透明 this->setWindowFlag(Qt::Framele ...
- Go:grpc
一.grpc安装 将 https://github.com/google/go-genproto 修改文件名放到 $GOPATH/src/google.golang.org/genproto 将 ht ...
- 面试28k职位,老乡面试官从HashCode到HashMap给我讲了一下午!「回家赶忙整理出1.6万字的面试材料」
作者:小傅哥 博客:https://bugstack.cn 目录 一.前言 二.HashCode为什么使用31作为乘数 1. 固定乘积31在这用到了 2. 来自stackoverflow的回答 3. ...