这一节介绍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的更多相关文章

  1. (转)hashmap hashtable 的区别 Hash table 内部的数据结构

    转自:http://www.cnblogs.com/carbs/archive/2012/07/04/2576995.html Hashtable 和 HashMap 做为 Map 的基本特性 两者都 ...

  2. hive内部表、外部表、分区表、视图

    1.Table 内部表 1).与数据库中的Table在概念上是类似的 2).每一个Table在Hive中都有一个相应的目录存储数据 3).所有的Table数据(不包括 External Table) ...

  3. html中 table的结构 彻底搞清 caption th thead等

    正因为有太多 随意 称呼的 教法, 所以 感到很困惑, 如, 很多人把th叫标题. 那人家 caption怎么想, th只是一个跟td一样的角色, 只是对他进行加粗 加黑了而已, 用于某些单元格的内容 ...

  4. Java finally语句到底是在return之前还是之后执行(JVM字节码分析及内部体系结构)?

    之前看了一篇关于"Java finally语句到底是在return之前还是之后执行?"这样的博客,看到兴致处,突然博客里的一个测试用例让我产生了疑惑. 测试用例如下: public ...

  5. hadoop笔记之Hive的数据存储(内部表)

    Hive的数据存储(内部表) Hive的数据存储(内部表) 基于HDFS 可使用hadoop给我们提供的web管理工具查看数据.打开管理工具localhost:9000–>Utilities下的 ...

  6. data.table 中的动态作用域

    data.table 中最常用的语法就是 data[i, j, by],其中 i.j 和 by 都是在动态作用域中被计算的.换句话说,我们不仅可以直接使用列,也可以提前定义诸如 .N ..I 和 .S ...

  7. jquery table 发送两次请求 解惑

    版本1.10 以下链接为一个较低版本解决方案: http://blog.csdn.net/anmo/article/details/17083125 而我的情况有点作, 情况描述: 1,一个页面两个t ...

  8. 使用element-ui的table组件时,渲染为html格式

    背景 今天在做vue的项目时,使用到 element-ui 的 table 组件,使用富文本编辑器进行新增操作后,发现 html格式 并没有被识别 原因 在 element-ui 中,table组件默 ...

  9. openresty开发系列19--lua的table操作

    openresty开发系列19--lua的table操作 Lua中table内部实际采用哈希表和数组分别保存键值对.普通值:下标从1开始 不推荐混合使用这两种赋值方式. local color={fi ...

随机推荐

  1. C#LeetCode刷题之#202-快乐数(Happy Number)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3856 访问. 编写一个算法来判断一个数是不是"快乐数& ...

  2. windows下的Redis安装:

    windows下的Redis安装: 百度网盘地址:https://pan.baidu.com/s/1yYED2pXLWolPXvWaABtF2Q 提取密码:xshu 1.解压文件并且创建start.b ...

  3. JavaScript 基础四

    遍历对象的属性 for...in 语句用于对数组或者对象的属性进行循环操作. for (变量 in 对象名字) { 在此执行代码 } 这个变量是自定义 符合命名规范 但是一般我们 都写为 k 或则 k ...

  4. CopyOnWriteArrayList源码阅读笔记

    简介 ArrayList是开发中使用比较多的集合,它不是线程安全的,CopyOnWriteArrayList就是线程安全版本的ArrayList.CopyOnWriteArrayList同样是通过数组 ...

  5. 用WEB方式开发WPF桌面程序

    因为疫情影响,公司裁员,结束了一年多的web开发经历,重新开始做桌面,新公司用的是WPF(居然用的是winform style...),当然这跟本文没有关系...上篇博客写的用后台api和前台浏览器控 ...

  6. Android 用versionName判断版本大小(是否进行版本更新)

    一般情况下都是用versionCode进行版本大小的判断从而进行判断是否进行app的更新,但是有可能从网站上爬下来的versionCode不准确,有的网站叫做build,所以用versionName进 ...

  7. Cannot instantiate the type ......的解决

    使用public abstract class MainWindow implements ActionListener{} 之后创建对象MainWindow window = new MainWin ...

  8. Scala中的Map集合

    1. Map集合 1.1 Scala中的Map介绍 Scala中的Map 和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的M ...

  9. 2 Spark角色介绍及运行模式

    第2章 Spark角色介绍及运行模式 2.1 集群角色 从物理部署层面上来看,Spark主要分为两种类型的节点,Master节点和Worker节点:Master节点主要运行集群管理器的中心化部分,所承 ...

  10. Vue管理系统前端系列四组件拆分封装

    目录 组件封装 首页布局拆分后结构 拆分后代码 状态管理中添加 app 模块 组件封装 在上一篇记录中,首页中有太多的代码,为了避免代码的臃肿,需要对主要的功能模块拆分,来让代码看起来更简洁,且能进行 ...