这一节介绍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. 【建议收藏】swoft的最佳实践

    这是一篇使用 swoft 两个月后的总结文章!,后续会陆续更新的 这是 web-api 开发的总结,如果使用 websocket 等服务的可能不适用,本章节会对一些规范.习惯,或者优化进行一些说明 一 ...

  2. Vue源码解析,keep-alive是如何实现缓存的?

    前言 在性能优化上,最常见的手段就是缓存.对需要经常访问的资源进行缓存,减少请求或者是初始化的过程,从而降低时间或内存的消耗.Vue 为我们提供了缓存组件 keep-alive,它可用于路由级别或组件 ...

  3. 『JWT』,你必须了解的认证登录方案

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  4. setjmp()/longjmp()的使用方法

    setjmp和longjmp.为了让你实现复杂的流控制,程序在系统里面运行完全依靠内存(代码段,全局段,堆存储器,栈存储器)和寄存器的内容(栈指针,基地址,计数器),setjmp保存当前的寄存器里面的 ...

  5. 为什么LinkedList不建议使用for循环遍历,而使用iterator方式进行遍历,但ArrayList建议使用for循环进行遍历呢?

    如果使用for循环方式遍历链表,由于链表中元素是通过指针连接彼此的,不存在索引的概念,如果使用for循环方式遍历LinkedList,依次传入索引值,则就相当于每次都要将链表撸一遍. 如:在下面的这个 ...

  6. Jmeter 常用函数(12)- 详解 __machineName

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 返回机器(电脑)名称 语法格式 ${_ ...

  7. Dubbo系列之 (五)服务订阅(2)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

  8. kernel 4.18.18 rpm 制作

    1.下载kernel源码: wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.18.18.tar.gz 2.解压源码 ...

  9. 虚拟化技术之kvm WEB管理工具kimchi

    在前面的博客中,我们介绍了kvm的各种工具,有基于图形管理的virt-manager.有基于命令行管理的virt-install .qemu-kvm.virsh等等:今天我们来介绍一款基于web界面的 ...

  10. computed&watch

    computed 定义:根据其他属性被计算出来的值 computed :{ [key: string]: Function | { get: Function, set: Function } } 是 ...