当前位置: 首页 > 编程日记 > 正文

PHP内核中的哈希表结构

https://github.com/HonestQiao/tipi/commit/17ca680289e490763a6a402f79afa2a13802bb36

下载:https://github.com/HonestQiao/tipi/tree/master/book/sample/chapt03

原文地址:http://www.nowamagic.net/librarys/veda/detail/1344

PHP中使用最为频繁的数据类型非字符串和数组莫属,PHP比较容易上手也得益于非常灵活的数组类型。 在开始详细介绍这些数据类型之前有必要介绍一下哈希表(HashTable)。 哈希表是PHP实现中尤为关键的数据结构。

哈希表在实践中使用的非常广泛,例如编译器通常会维护的一个符号表来保存标记,很多高级语言中也显式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),删除(Delete)等操作,这些操作在最坏的情况下和链表的性能一样为O(n)。 不过通常并不会这么坏,合理设计的哈希算法能有效的避免这类情况,通常哈希表的这些操作时间复杂度为O(1)。 这也是它被钟爱的原因。

正是因为哈希表在使用上的便利性及效率上的表现,目前大部分动态语言的实现中都使用了哈希表。

为了方便读者阅读后面的内容,这里提前列举一下HashTable实现中出现的基本概念。 哈希表是一种通过哈希函数,将特定的键映射到特定值的一种数据结构,它维护键和值之间一一对应关系。

  • 键(key):用于操作数据的标示,例如PHP数组中的索引,或者字符串键等等。
  • 槽(slot/bucket):哈希表中用于保存数据的一个单元,也就是数据真正存放的容器。
  • 哈希函数(hash function):将key映射(map)到数据应该存放的slot所在位置的函数。
  • 哈希冲突(hash collision):哈希函数将两个不同的key映射到同一个索引的情况。

哈希表可以理解为数组的扩展或者关联数组,数组使用数字下标来寻址,如果关键字(key)的范围较小且是数字的话, 我们可以直接使用数组来完成哈希表,而如果关键字范围太大,如果直接使用数组我们需要为所有可能的key申请空间。 很多情况下这是不现实的。即使空间足够,空间利用率也会很低,这并不理想。同时键也可能并不是数字, 在PHP中尤为如此,所以人们使用一种映射函数(哈希函数)来将key映射到特定的域中:

h(key) -> index

通过合理设计的哈希函数,我们就能将key映射到合适的范围,因为我们的key空间可以很大(例如字符串key), 在映射到一个较小的空间中时可能会出现两个不同的key映射被到同一个index上的情况, 这就是我们所说的出现了冲突。 目前解决hash冲突的方法主要有两种:链接法和开放寻址法。

冲突解决

链接法:链接法通过使用一个链表来保存slot值的方式来解决冲突,也就是当不同的key映射到一个槽中的时候使用链表来保存这些值。 所以使用链接法是在最坏的情况下,也就是所有的key都映射到同一个槽中了,操作链表的时间复杂度为O(n)。 所以选择一个合适的哈希函数是最为关键的。目前PHP中HashTable的实现就是采用这种方式来解决冲突的。

开放寻址法:通常还有另外一种解决冲突的方法:开放寻址法。使用开放寻址法是槽本身直接存放数据, 在插入数据时如果key所映射到的索引已经有数据了,这说明发生了冲突,这是会寻找下一个槽, 如果该槽也被占用了则继续寻找下一个槽,直到寻找到没有被占用的槽,在查找时也使用同样的策律来进行。

哈希表的实现

在了解到哈希表的原理之后要实现一个哈希表也很容易,主要需要完成的工作只有三点:

  1. 实现哈希函数
  2. 冲突的解决
  3. 操作接口的实现

首先我们需要一个容器来保存我们的哈希表,哈希表需要保存的内容主要是保存进来的的数据, 同时为了方便的得知哈希表中存储的元素个数,需要保存一个大小字段, 第二个需要的就是保存数据的容器了。作为实例,下面将实现一个简易的哈希表。基本的数据结构主要有两个, 一个用于保存哈希表本身,另外一个就是用于实际保存数据的单链表了,定义如下:

typedef struct _Bucket
{char *key;void *value;struct _Bucket *next;} Bucket;typedef struct _HashTable
{int size;Bucket* buckets;
} HashTable;


上面的定义和PHP中的实现类似,为了便于理解裁剪了大部分无关的细节,在本节中为了简化, key的数据类型为字符串,而存储的数据类型可以为任意类型。

Bucket结构体是一个单链表,这是为了解决多个key哈希冲突的问题,也就是前面所提到的的链接法。 当多个key映射到同一个index的时候将冲突的元素链接起来。

哈希函数需要尽可能的将不同的key映射到不同的槽(slot或者bucket)中,首先我们采用一种最为简单的哈希算法实现: 将key字符串的所有字符加起来,然后以结果对哈希表的大小取模,这样索引就能落在数组索引的范围之内了。

static int hash_str(char *key)
{int hash = 0;char *cur = key;while(*(cur++) != '\0') {hash += *cur;}return hash;
}// 使用这个宏来求得key在哈希表中的索引
#define HASH_INDEX(ht, key) (hash_str((key)) % (ht)->size)


这个哈希算法比较简单,它的效果并不好,在实际场景下不会使用这种哈希算法, 例如PHP中使用的是称为DJBX33A算法, 这里列举了Mysql,OpenSSL等开源软件使用的哈希算法, 有兴趣的读者可以前往参考。

操作接口的实现

为了操作哈希表,实现了如下几个操作函数:

int hash_init(HashTable *ht);                               // 初始化哈希表
int hash_lookup(HashTable *ht, char *key, void **result);   // 根据key查找内容
int hash_insert(HashTable *ht, char *key, void *value);     // 将内容插入到哈希表中
int hash_remove(HashTable *ht, char *key);                  // 删除key所指向的内容
int hash_destroy(HashTable *ht);


下面以插入和获取操作函数为例:

int hash_insert(HashTable *ht, char *key, void *value)
{// check if we need to resize the hashtableresize_hash_table_if_needed(ht);    // 哈希表不固定大小,当插入的内容快占满哈表的存储空间// 将对哈希表进行扩容, 以便容纳所有的元素int index = HASH_INDEX(ht, key);    // 找到key所映射到的索引Bucket *org_bucket = ht->buckets[index];Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); // 为新元素申请空间bucket->key   = strdup(key);// 将值内容保存进来, 这里只是简单的将指针指向要存储的内容,而没有将内容复制。bucket->value = value;  LOG_MSG("Insert data p: %p\n", value);ht->elem_num += 1; // 记录一下现在哈希表中的元素个数if(org_bucket != NULL) { // 发生了碰撞,将新元素放置在链表的头部LOG_MSG("Index collision found with org hashtable: %p\n", org_bucket);bucket->next = org_bucket;}ht->buckets[index]= bucket;LOG_MSG("Element inserted at index %i, now we have: %i elements\n",index, ht->elem_num);return SUCCESS;
}


上面这个哈希表的插入操作比较简单,简单的以key做哈希,找到元素应该存储的位置,并检查该位置是否已经有了内容, 如果发生碰撞则将新元素链接到原有元素链表头部。在查找时也按照同样的策略,找到元素所在的位置,如果存在元素, 则将该链表的所有元素的key和要查找的key依次对比, 直到找到一致的元素,否则说明该值没有匹配的内容。

int hash_lookup(HashTable *ht, char *key, void **result)
{int index = HASH_INDEX(ht, key);Bucket *bucket = ht->buckets[index];if(bucket == NULL) return FAILED;// 查找这个链表以便找到正确的元素,通常这个链表应该是只有一个元素的,也就不用多次// 循环。要保证这一点需要有一个合适的哈希算法,见前面相关哈希函数的链接。while(bucket){if(strcmp(bucket->key, key) == 0){LOG_MSG("HashTable found key in index: %i with  key: %s value: %p\n",index, key, bucket->value);*result = bucket->value;    return SUCCESS;}bucket = bucket->next;}LOG_MSG("HashTable lookup missed the key: %s\n", key);return FAILED;
}


PHP中数组是基于哈希表实现的,依次给数组添加元素时,元素之间是有先后顺序的,而这里的哈希表在物理位置上显然是接近平均分布的, 这样是无法根据插入的先后顺序获取到这些元素的,在PHP的实现中Bucket结构体还维护了另一个指针字段来维护元素之间的关系。 具体内容在后一小节PHP中的HashTable中进行详细说明。上面的例子就是PHP中实现的一个精简版。

延伸阅读

此文章所在专题列表如下:

  1. PHP内核探索:从SAPI接口开始
  2. PHP内核探索:一次请求的开始与结束
  3. PHP内核探索:一次请求生命周期
  4. PHP内核探索:单进程SAPI生命周期
  5. PHP内核探索:多进程/线程的SAPI生命周期
  6. PHP内核探索:Zend引擎
  7. PHP内核探索:再次探讨SAPI
  8. PHP内核探索:Apache模块介绍
  9. PHP内核探索:通过mod_php5支持PHP
  10. PHP内核探索:Apache运行与钩子函数
  11. PHP内核探索:嵌入式PHP
  12. PHP内核探索:PHP的FastCGI
  13. PHP内核探索:如何执行PHP脚本
  14. PHP内核探索:PHP脚本的执行细节
  15. PHP内核探索:操作码OpCode
  16. PHP内核探索:PHP里的opcode
  17. PHP内核探索:解释器的执行过程
  18. PHP内核探索:变量概述
  19. PHP内核探索:变量存储与类型
  20. PHP内核探索:PHP中的哈希表
  21. PHP内核探索:理解Zend里的哈希表
  22. PHP内核探索:PHP哈希算法设计
  23. PHP内核探索:翻译一篇HashTables文章
  24. PHP内核探索:哈希碰撞攻击是什么?
  25. PHP内核探索:常量的实现
  26. PHP内核探索:变量的存储
  27. PHP内核探索:变量的类型
  28. PHP内核探索:变量的值操作
  29. PHP内核探索:变量的创建
  30. PHP内核探索:预定义变量
  31. PHP内核探索:变量的检索
  32. PHP内核探索:变量的类型转换
  33. PHP内核探索:弱类型变量的实现
  34. PHP内核探索:静态变量的实现
  35. PHP内核探索:变量类型提示
  36. PHP内核探索:变量的生命周期
  37. PHP内核探索:变量赋值与销毁
  38. PHP内核探索:变量作用域
  39. PHP内核探索:诡异的变量名
  40. PHP内核探索:变量的value和type存储
  41. PHP内核探索:全局变量Global
  42. PHP内核探索:变量类型的转换
  43. PHP内核探索:内存管理开篇
  44. PHP内核探索:Zend内存管理器
  45. PHP内核探索:PHP的内存管理
  46. PHP内核探索:内存的申请与销毁
  47. PHP内核探索:引用计数与写时复制
  48. PHP内核探索:PHP5.3的垃圾回收机制
  49. PHP内核探索:内存管理中的cache
  50. PHP内核探索:写时复制COW机制
  51. PHP内核探索:数组与链表
  52. PHP内核探索:使用哈希表API
  53. PHP内核探索:数组操作
  54. PHP内核探索:数组源码分析
  55. PHP内核探索:函数的分类
  56. PHP内核探索:函数的内部结构
  57. PHP内核探索:函数结构转换
  58. PHP内核探索:定义函数的过程
  59. PHP内核探索:函数的参数
  60. PHP内核探索:zend_parse_parameters函数
  61. PHP内核探索:函数返回值
  62. PHP内核探索:形参return value
  63. PHP内核探索:函数调用与执行
  64. PHP内核探索:引用与函数执行
  65. PHP内核探索:匿名函数及闭包
  66. PHP内核探索:面向对象开篇
  67. PHP内核探索:类的结构和实现
  68. PHP内核探索:类的成员变量
  69. PHP内核探索:类的成员方法
  70. PHP内核探索:类的原型zend_class_entry
  71. PHP内核探索:类的定义
  72. PHP内核探索:访问控制
  73. PHP内核探索:继承,多态与抽象类
  74. PHP内核探索:魔术函数与延迟绑定
  75. PHP内核探索:保留类与特殊类
  76. PHP内核探索:对象
  77. PHP内核探索:创建对象实例
  78. PHP内核探索:对象属性读写
  79. PHP内核探索:命名空间
  80. PHP内核探索:定义接口
  81. PHP内核探索:继承与实现接口
  82. PHP内核探索:资源resource类型
  83. PHP内核探索:Zend虚拟机
  84. PHP内核探索:虚拟机的词法解析
  85. PHP内核探索:虚拟机的语法分析
  86. PHP内核探索:中间代码opcode的执行
  87. PHP内核探索:代码的加密与解密
  88. PHP内核探索:zend_execute的具体执行过程
  89. PHP内核探索:变量的引用与计数规则
  90. PHP内核探索:新垃圾回收机制说明

相关文章:

应聘苹果数据科学家,你需要知道些什么?

作者 | Jay Feng译者 | 孙薇,责编 | 屠敏头图 | CSDN 下载自东方 IC出品 | CSDN(ID:CSDNnews)以下为译文:苹果公司是全球最大的技术公司之一,从事电子消费产品、计算机软件以及在线服务的设计、开发并销售工…

python 利用模板文件生成配置文件

2019独角兽企业重金招聘Python工程师标准>>> gen.py: __author__ fuhan from jinja2 import Template a{name:a} b{name:b} mode_dict { a:a, b:b } def gen_config(tplt_file, modea): with open(tplt_file, r) as r: tplt Template(r.read()) config mode_dic…

利用Apache的ab命令做Benchmark性能测试

测试系统性能,例如httpsqs # ab -k -c 10 -n 100000 "http://127.0.0.1:1218/?namexoyo&optput&dataabc ab是Apache超文本传输协议(HTTP)的性能测试工具。 其设计意图是描绘当前所安装的Apache的执行性能,主要是显示你安装的Apache每秒可…

MySQL 狠甩 Oracle 稳居 Top1,私有云最受重用,大数据人才匮乏! | 中国大数据应用年度报告...

整理 | 屠敏出品 | CSDN(ID:CSDNnews)科技长河,顺之者昌,错失者亡。在这个技术百态之中,中国专业的 IT 社区CSDN 创始人&董事长蒋涛曾多次在公开活动中表示,开发者是对技术变革最敏感的人群。这不仅源于…

MAC安装OpenXenManager管理Xenserver

官方文档:https://github.com/OpenXenManager/openxenmanager要求:Python 2.7pyGTK 2.16ConfigObjRavenGTK-VNC(仅限Linux)Debian / Ubuntu Linux软件包依赖项:python2.7 python-gtk2 glade python-gtk-vnc python-gla…

用Flutter + Dart快速构建一款绝美移动App

作者 | Wojciech Kuroczycki译者 | 弯月来源 | CSDN(ID:CSDNnews)如今,与前端或移动相关的新框架层出不穷。所有从事Web开发的人都应该熟悉各种目不暇接的新方法以及针对复杂问题的轻量级解决方案。我们不再因为没有现成的技术而烦恼&#xf…

自己写的单链表

link.c #include <stdio.h> #include <malloc.h> #include <string.h> #include <stdlib.h> #include "link.h"/**** 这是一个计算HASH值的算法**/ int time33(char* arKey,int arlength){int h 0;int i;for(i0;i<arlength;i){h h*3…

假装不知道有尽头(博弈论的诡计)

《笑林广记》中记载这样一则笑话。 有一个人去理发铺剃头&#xff0c;剃头匠给他剃得很草率。剃完后&#xff0c;这人却付给剃头匠双倍的钱&#xff0c;什么也没说就走了。一个多月后的一天&#xff0c;这人又来理发铺剃头。剃头匠还记得他上次多付了钱&#xff0c;觉得此人阔绰…

Java Script 第四节课 Java Script的隐式转换

<!DOCTYPE html><html><head><meta charset"utf-8"><title></title><script type"text/javascript">/*if(exp){exp为true的代码段;}else{exp为false的代码段;}*///其它类型转换成布尔类型假的有var a;//undefin…

深入理解malloc和free

1.为什么free是void*&#xff0c;那么它怎么知道要释放多少内存&#xff1f; 《UNIX环境高级编程》 《C语言编程常见问题解答》 《你必须知道的495个C语言问题》 《UNIX环境高级编程》 2.free源码 内存控制块结构定义 struct mem_control_block {int is_available;int si…

根据IP和MAC查端口

进入交换机的命令提示符.输入show ip arp 查出IP地址跟MAC 地址的对照表.再输入show mac-address-table,看一下这个MAC是从哪个端口学到的转载于:https://blog.51cto.com/124130/271033

“数学不好,干啥都不行!”骨灰级程序员:其实你们都是瞎努力!

之前很多程序员读者向我们反馈&#xff1a;1&#xff09;数据结构、编程语句&#xff0c;核心原理都是数学&#xff0c;不会数学搞编程好难&#xff0c;后来发现各种东西还要概率论&#xff0c;还要推收敛&#xff01;近似还要知道泰勒展开&#xff01;2&#xff09;做算法优化…

转:秒杀系统架构分析与实战

原文出处&#xff1a; 陶邦仁 欢迎分享原创到伯乐头条 0 系列目录 秒杀系统架构 秒杀系统架构分析与实战1 秒杀业务分析 正常电子商务流程 &#xff08;1&#xff09;查询商品&#xff1b;&#xff08;2&#xff09;创建订单&#xff1b;&#xff08;3&#xff09;扣减库存&a…

Visual Studio中的《C# 语言规范》

无意中的无意发现了个好东西——《C# 语言规范》&#xff0c;您不用到处下载&#xff0c;它就在您的Visual Studio安装目录中&#xff0c;例如&#xff1a;F:\Program Files\Microsoft Visual Studio 9.0\VC#\Specifications\2052\CSharp Language Specification.doc 这是它的目…

超轻量级中文OCR,支持竖排文字识别、ncnn推理,总模型仅17M

整理 | AI科技大本营光学字符识别&#xff08;OCR&#xff09;技术已经得到了广泛应用。比如发票上用来识别关键字样&#xff0c;搜题App用来识别书本上的试题。近期&#xff0c;这个叫做chineseocr_lite的OCR项目开源了&#xff0c;这是一个超轻量级中文ocr&#xff0c;支持竖…

Redis队列的应用

Redis用双链表list实现队列的 LPUSH key value [value ...] 将一个或多个值 value 插入到列表 key 的表头 如果有多个 value 值&#xff0c;那么各个 value 值按从左到右的顺序依次插入到表头&#xff1a; 比如说&#xff0c;对空列表 mylist 执行命令 LPUSH mylist a b c &…

Python fabric实现远程操作和部署

fabrictitle是开发&#xff0c;但是同时要干开发测试还有运维的活 (o(╯□╰)o)近期接手越来越多的东西&#xff0c;发布和运维的工作相当机械&#xff0c;加上频率还蛮高&#xff0c;导致时间浪费还是优点多。修复bug什么的&#xff0c;测试&#xff0c;提交版本库(2分钟)&…

自己写的哈希表以及解决哈希冲突

哈希表就是键值key-value对&#xff0c;使用hash函数让key产生哈希值&#xff0c;当不同的key产生相同的哈希值时就是哈希冲突了&#xff0c;产生哈希冲突可以使用拉链法。 hash.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include &…

Python与MySQL数据库的交互实战

作者 | Huang supreme编辑 | 郭芮图源 | 视觉中国安装PyMySQL库如果你想要使用python操作MySQL数据库&#xff0c;就必须先要安装pymysql库&#xff0c;这个库的安装很简单&#xff0c;直接使用pip install pymysql&#xff1b;假如这种方式还是安装不上&#xff0c;就用如下链…

Hyper-V的三种网卡

External 虚拟机和物理网络、本地主机都能通信 Internal 虚拟机之间互相通信&#xff0c;并且虚拟机能和本机通信 Private 仅允许运行在这台物理机上的虚拟机之间互相通信

filter-mapping中的dispatcher使用

web.xml里<filter-mapping>中的<dispatcher>作用 2.4版本的servlet规范在部属描述符中新增加了一个<dispatcher>元素&#xff0c;这个元素有四个可能的值&#xff1a;即 REQUEST,FORWARD,INCLUDE和ERROR 可以在一个<filter-mapping>元素中加入任意数目…

脉冲神经网络在目标检测的首次尝试,性能堪比CNN | AAAI 2020

译者 | VincentLee来源 | 晓飞的算法工程笔记脉冲神经网络(Spiking neural network, SNN)将脉冲神经元作为计算单元&#xff0c;能够模仿人类大脑的信息编码和处理过程。不同于CNN使用具体的值(continuous)进行信息传递&#xff0c;SNN通过脉冲序列(discrete)中每个脉冲发射时…

TCMalloc:线程缓存的Malloc

转载自&#xff1a; http://shiningray.cn/tcmalloc-thread-caching-malloc.html作者&#xff1a;Sanjay Ghemawat, Paul Menage 原文 翻译&#xff1a;ShiningRay 动机 TCMalloc要比glibc 2.3的malloc&#xff08;可以从一个叫作ptmalloc2的独立库获得&#xff09;和其他我测试…

今年央视的春晚能给人带来惊喜吗?

已经好多年还没看完中央电视台的春节联欢晚会自己就睡着了&#xff0c;说实在的&#xff0c;现在央视春节联欢晚会的节目总是让人期待后感到相当的平淡乏味&#xff0c;有些搞笑节目庸俗的让人笑不出来&#xff0c;绝大多数的节目都显得非常的人工&#xff0c;全然不能激发出观…

将baidu地图中的baidu logo去掉

Web 最简单方法&#xff0c;将logo的css样式改为display:none即可 <!DOCTYPE html> <html> <head><meta charset"utf-8" /><title>移除百度地图LOGO和版权信息</title><script type"text/javascript" src"htt…

Linux环境网络库

安装libevent 官网&#xff1a;http://libevent.org/ 书籍&#xff1a;http://www.wangafu.net/~nickm/libevent-book/ Libevent参考手册翻译:http://blog.csdn.net/laoyi19861011/article/category/831215 Libevent参考手册翻译增加&#xff1a;http://blog.sina.co…

万人马拉松赛事,人脸识别系统如何快速、准确完成校验?

作者 | 阿里文娱技术专家墨贤出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;大麦的人脸闸机在2019年杭州马拉松上成功的完成了刷脸入场功能的首秀&#xff0c;相比传统的马拉松入场核验方案在入场体验和入场效率上都有了很大的提升&#xff0c;下面介绍一下大麦的人…

Collection集合List、Set

Collection集合&#xff0c;用来保存一组数据的数据结构。 Collection是一个接口&#xff0c;定义了所有集合都应该包含的特征和行为 Collection派生出了两类集合 List和Set List接口&#xff1a;List集合的特征是元素是可重复且有序 Set接口&#xff1a;Set集合的特征是元素是…

如何用Jupyter Notebook制作新冠病毒疫情追踪器?

出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;新冠肺炎已在全球范围内爆发。为了解全球疫情分布情况&#xff0c;有技术人员使用Jupyter Notebook绘制了两种疫情的等值线地图&#xff08;choropleth chart&#xff09;和散点图。前者显示了一个国家/地区的疫情扩散…