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 ...
随机推荐
- C#LeetCode刷题之#202-快乐数(Happy Number)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3856 访问. 编写一个算法来判断一个数是不是"快乐数& ...
- windows下的Redis安装:
windows下的Redis安装: 百度网盘地址:https://pan.baidu.com/s/1yYED2pXLWolPXvWaABtF2Q 提取密码:xshu 1.解压文件并且创建start.b ...
- JavaScript 基础四
遍历对象的属性 for...in 语句用于对数组或者对象的属性进行循环操作. for (变量 in 对象名字) { 在此执行代码 } 这个变量是自定义 符合命名规范 但是一般我们 都写为 k 或则 k ...
- CopyOnWriteArrayList源码阅读笔记
简介 ArrayList是开发中使用比较多的集合,它不是线程安全的,CopyOnWriteArrayList就是线程安全版本的ArrayList.CopyOnWriteArrayList同样是通过数组 ...
- 用WEB方式开发WPF桌面程序
因为疫情影响,公司裁员,结束了一年多的web开发经历,重新开始做桌面,新公司用的是WPF(居然用的是winform style...),当然这跟本文没有关系...上篇博客写的用后台api和前台浏览器控 ...
- Android 用versionName判断版本大小(是否进行版本更新)
一般情况下都是用versionCode进行版本大小的判断从而进行判断是否进行app的更新,但是有可能从网站上爬下来的versionCode不准确,有的网站叫做build,所以用versionName进 ...
- Cannot instantiate the type ......的解决
使用public abstract class MainWindow implements ActionListener{} 之后创建对象MainWindow window = new MainWin ...
- Scala中的Map集合
1. Map集合 1.1 Scala中的Map介绍 Scala中的Map 和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的M ...
- 2 Spark角色介绍及运行模式
第2章 Spark角色介绍及运行模式 2.1 集群角色 从物理部署层面上来看,Spark主要分为两种类型的节点,Master节点和Worker节点:Master节点主要运行集群管理器的中心化部分,所承 ...
- Vue管理系统前端系列四组件拆分封装
目录 组件封装 首页布局拆分后结构 拆分后代码 状态管理中添加 app 模块 组件封装 在上一篇记录中,首页中有太多的代码,为了避免代码的臃肿,需要对主要的功能模块拆分,来让代码看起来更简洁,且能进行 ...