这一节介绍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. 【译】GitHub 为什么挂?官方的可行性报告为你解答

    本文翻译自 GitHub 官方博客<Introducing the GitHub Availability Report> 原文链接:https://github.blog/2020-07 ...

  2. 第二章 Kuberbetes实践指南

    kubernetes安装与配置 网络,安全,服务启动配置 参考: kubernetes权威指南第二版 kubectl命令行工具用法详解 kubectl [command] [type] [name] ...

  3. 记录一下navicat的快捷键

    1.ctrl+q           打开查询窗口2.ctrl+/            注释sql语句3.ctrl+shift +/  解除注释4.ctrl+r           运行查询窗口的s ...

  4. golang mysql demo

    Go操作Mysql数据库 使用Go操作MySQL等数据库,一般有两种方式:一是使用database/sql接口,直接在代码里硬编码sql语句:二是使用gorm,即对象关系映射的方式在代码里抽象的操作数 ...

  5. 经典游戏--24点--c++代码实现和总体思路(简单暴力向)

    24点 24点是一个非常经典的游戏,从扑克牌里抽4张牌,其中J=11,Q=12,K=13,然后经过+,-,*,/,(),的计算后,使得计算得值为24,例如抽到1,2,2,5四张牌,那么 (1+5)*( ...

  6. 牛客网PAT练兵场-查验身份证

    题解:模拟题,直接算 题目地址:https://www.nowcoder.com/questionTerminal/779a72a420744b1d9c0ec7b7a8dd8f39 /** * *作者 ...

  7. shell脚本同步私人git仓库

    前言 分别在个人电脑.个人服务器.码云三个地方建立了数据仓库用于保存自己的各种数据,通过git+shell进行数据同步. 此举不仅可以避免因存储损坏.版本更迭.数据误操作等因素带来的各种麻烦,也能实现 ...

  8. 医疗seo常用的图标工具

    http://www.wocaoseo.com/thread-304-1-1.html 下面是一些医疗seo常用的一些图表工具,这些都是些最简单的工具,主要放置这里以防止以后有作用. 1,医疗的常用搜 ...

  9. SpringBoot中关于Excel的导入和导出

    前言   由于在最近的项目中使用Excel导入和导出较为频繁,以此篇博客作为记录,方便日后查阅.本文前台页面将使用layui,来演示对Excel文件导入和导出的效果.本文代码已上传至我的gitHub, ...

  10. vue cli3如何引入全局less变量

    最近在项目中需要写一个全局的style.less,然后在各组件中可以直接调用: 1.在assets下创建一个less文件: 2.安装style-resources-loader (npm i styl ...