这一节介绍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. unity3d学习笔记(一) 第一人称视角实现和倒计时实现

    unity3d学习笔记(一) 第一人称视角实现和倒计时实现 1. 第一人称视角 (1)让mainCamera和player(视角对象)同步在一起 因为我们的player是生成的,所以不能把mainCa ...

  2. windows安装cnpm步骤

    1.首先前往nodejs官网下载nodejs 2.安装nodejs 3.打开cmd,输入npm -v,检查npm是否安装成功.成功返回的话返回输出版本号 4.安装cnpm,输入npm install ...

  3. 使用部分函数时并未include其所在头文件,但是能编译成功且能运行,为什么?

    最近在看APUE,试了上面的一些例子,其中有个例子是使用getpid函数获取进程id,但是在我写demo时,并未引入其所在的头文件unistd.h,结果也能编译成功,也能运行,于是就琢磨下为啥. En ...

  4. [noip2002] 产生数

    题目描述 给出一个整数 n (n<1030)和 k 个变换规则 (k < 15) . 规则: 一位数可变换成另一个一位数: 规则的右部不能为零. 例如:n = 234 .有规则( k=2  ...

  5. CODING DevOps 微服务项目实战系列第一课,明天等你

    CODING DevOps 微服务项目实战系列第一课<DevOps 微服务项目实战:DevOps 初体验>将由 CODING DevOps 开发工程师 王宽老师 向大家介绍 DevOps ...

  6. vue watch 和 computed 区别与使用

    目录 computed 和 watch 的说明 与 区别 computed 计算属性说明: watch 监听属性说明: watch 和 computed 的区别是: 使用 参考官方文档 compute ...

  7. ssh连接:Socket error Event: 32 Error: 10053.

    今天在使用xshell连接刚装的linux系统的时候,发现无法建立连接,会报如下错误: Connecting to 192.168.21x.x:22...Connection established. ...

  8. Qt 实现 异形 窗体&按钮

    //关键部分代码如下//设置异形窗体 //setWindowOpacity(0.5);//设置窗体透明度 0完全透明,1完全不透明 this->setWindowFlag(Qt::Framele ...

  9. Go:grpc

    一.grpc安装 将 https://github.com/google/go-genproto 修改文件名放到 $GOPATH/src/google.golang.org/genproto 将 ht ...

  10. 面试28k职位,老乡面试官从HashCode到HashMap给我讲了一下午!「回家赶忙整理出1.6万字的面试材料」

    作者:小傅哥 博客:https://bugstack.cn 目录 一.前言 二.HashCode为什么使用31作为乘数 1. 固定乘积31在这用到了 2. 来自stackoverflow的回答 3. ...