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 ...
随机推荐
- shazidouhui的使用体验
这个软件有三个界面,一个是我的,一个是已批改作业,一个是未完成作业,但是,我不知道怎么用除了“我的”这个界面以外的其他两个界面,这一点还是需要优化优化,另外就是登陆界面确实还存在一点问题,有待改进.
- 坚持第一天:HashMap和Hashtable的区别
其实,到底是用HashMap和Hashtable主要看需求, 1.它们俩都共同实现了:Map接口,但是Hashtable实现是基于Dictionary抽象类的,在java5的时候提供了Concurre ...
- noip复习——逆元
逆元,即对给定\(a,p\ (a \perp p)\),求\(x\)使得\(ax \equiv 1 \ (\bmod p)\) 逆元可以看做\(a\)在模\(p\)意义下的\(a^{-1}\).因此, ...
- CVT1100 错误的修复 2009-10-12 11:38
我们在用VS2005编译MFC工程时极少会出现如下错误: 一,CVTRES : fatal error CVT1100: 重复的资源.type:MANIFEST, name:1, language:0 ...
- 手牵手,使用uni-app从零开发一款视频小程序 (系列上 准备工作篇)
系列文章 手牵手,使用uni-app从零开发一款视频小程序 (系列上 准备工作篇) 手牵手,使用uni-app从零开发一款视频小程序 (系列下 开发实战篇) 前言 好久不见,很久没更新博客了,前段时间 ...
- Oracle 多条数据转一行逗号隔开
wm_concat 例: select wm_concat(市) from pa50 where apa132=省
- hook框架-frida使用-APP在模拟器无法打开,用钩子去除限制
app拿soul为例子 一.环境配置 #模拟器的frida服务为86 #frida-server-12.9.8-android-x86 adb push frida-server-12.9.8-and ...
- springMVC入门(六)------json交互与RESTFul风格支持
简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.由于其简单易用,目前常用来通过AJAX与后台进行交互.springMVC对于接收.发送JSON数据也 ...
- Bagging与随机森林(RF)算法原理总结
Bagging与随机森林算法原理总结 在集成学习原理小结中,我们学习到了两个流派,一个是Boosting,它的特点是各个弱学习器之间存在依赖和关系,另一个是Bagging,它的特点是各个弱学习器之间没 ...
- neighbor和neigh_modify(转载)
(转载:http://blog.sina.com.cn/s/blog_b48a7ac30102w4mg.html###) 以下取自:http://simulation.haotui.com/viewt ...