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刷题之#35-搜索插入位置(Search Insert Position)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3979 访问. 给定一个排序数组和一个目标值,在数组中找到目标值, ...
- 封装react antd的form表单组件
form表单在我们日常的开发过程中被使用到的概率还是很大的,比如包含了登录.注册.修改个人信息.新增修改业务数据等的公司内部管理系统.而在使用时这些表单的样式如高度.上下边距.边框.圆角.阴影.高亮等 ...
- Spring Security报异常 Encoded password does not look like BCrypt
控制台报错: Encoded password does not look like BCrypt 意思是前端传回去的密码格式与数据库里的密码格式不匹配,这样即使密码正确也无法校验.自然也就无法登录. ...
- MQC集群配置错误
这个错误说明配置里面,MQC环境配置错了 运行war包时会读到本地的配置system-config.propertites文件
- 性能测试必备知识(11)- 怎么理解内存中的Buffer和Cache?
做性能测试的必备知识系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html 缓存 从 free 命令可以看到,缓存其实就 ...
- Android 使用Zxing报错:Channel is unrecoverably broken and will be disposed!
使用Zxing的扫描二维码库,修改成从相册识别二维码图片,根据网上的demo修改,继而在我使用的fragment报错Channel is unrecoverably broken and will b ...
- Java之reflection(反射机制)——通过反射操作泛型,注解
一.反射操作泛型(Generic) Java采用泛型擦除机制来引入泛型.Java中的泛型仅仅是给编译器Javac使用的,确保数据的安全性和免去强制类型转换的麻烦.但是编译一旦完成,所有和泛型有关的类型 ...
- Jmeter 常用函数(1)- 详解 __Random
如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 产生一个随机数 语法格式 ${__Ra ...
- 第1章 Hive入门
第1章 Hive入门 1.1 什么是Hive Hive:由Facebook开源用于解决海量结构化日志的数据统计. Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提 ...
- Gitlab安装使用
Gitlab安装使用 1. 为什么要使用gitlab Git的优点多多这里就不详细介绍了: Git是版本控制系统,Github是在线的基于Git的代码托管服务: Github有个小缺陷 (也不能算是缺 ...