关于Hash Table专题:

一直想深入理解一下php的hash table的实现,以前一直是星星点点的看看,从未彻底的总结过,那就从这个专题开始吧!

主要想总结几个部分:hashtable结构,hashtable实现,hashtable使用。

参考博客:

现代魔法学院 :http://www.nowamagic.net/academy/detail/1200001

Veda原型:http://www.nowamagic.net/librarys/veda/detail/1348

猫爷:http://songqi.sinaapp.com/blog/category/php-develop/

鸟哥:http://www.laruence.com/2009/08/23/1065.html

let's go!

Hash Table的结构图:

在上图中发现:Bucket1和Bucket2是hash冲突的双向链表,但是后添加的Bucket2是添加到头部的,可以看到Bucket2的pListLast和pNext指向Bucket1。(增加元素的时候, 元素会插在相同Hash元素链的头部和线性列表的尾部--鸟哥)

对HashTable结构体的字段解释:

1.nTableSize。整个哈希表分配的大小(在内部实现的C中分配的数组大小,PHP是动态的,但到底层数组是有大小的是静态的),他的大小有一个固定的申请算法,nTableSize = pow(ceil(log(nTableSize,2))),举个例子来看,如果PHP数组存储32个整形数据,那么底层申请的nTableSize应该等于32个元素,如果33呢,那么取最近且大于这个数的一个数64,那么分配的大小是64个元素。这样分配的原因是为了能分配足够的内存同样又不会浪费太多的内存。基于哈希的效率考虑,太小那么势必造成哈希之后太多的碰撞查找,如果分配太大那么必然浪费太多内存,这样分配经过实践证明相对在空间和时间上可以获得一个平衡。

2.nTableMask。哈希表的掩码数值等于nTableSize-1,他的作用是什么?用来纠正通过DBJ算法计算的哈希值在当前nTableSize大小的哈希表中的正确的索引值。比如"foo"通过固定算法之后得出的哈希值是193491849,如果表的大小为64,很明显已经超过了最大索引值,这时候就需要运用哈希表的掩码对其进行矫正实际采用的方法就是与掩码进行位与运算,这样做是为了把哈希值大的一样映射到nTalbeSize空间内。

   hash  |    |   0b1011100010000111001110001001
& mask | & | & 0b0000000000000000000000111111
---------------------------------------------------------
= index | = | = 0b0000000000000000000000001001

3.nNumOfElements。是PHP数组中实际存储元素的个数,我们使用count,sizeof计算的就是获取的这个值。

4.nNextFreeElement。下一个空闲的元素空间,当我们申请一个空下标元素的时候就需要用到此项,比如$ret[] = 'apple'。

5.pInternalPointer。存储了内部当前执行的元素的指针,当我们使用一些内部循环函数的时候会用到这个指针比如reset(), current(), prev(), next(), foreach(), end()。

6.pListHead和pListTail则具体指向了该哈希表的第一个和最后一个元素,对应就是数组的起始和结束元素。

7.arBuckets。这个就是实际存储的C的内部数组。这里记录的是一个指向指针的指针Bucket **。即指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。

8.pDestructor 是一个析构函数,当某个值被从哈希表删除的时候会触发此函数。他还有一个主要作用是用于变量的GC回收。在PHP里面GC是通过引用计数实现的,当一个变量的引用计数变为0,就会被PHP的GC回收。

9.persistent 定义了hashtable是否能在多次request中获得持久存在。

10.nApplyCount 和 bApplyProtection 是用来防止无限递归的。关于nApplyCount的意义, 我们可以通过一个例子来了解:

<?php
$arr = array(1,2,3,4,5,);
$arr[] = &$arr; var_export($arr); //Fatal error: Nesting level too deep - recursive dependency?

这个字段就是为了防治循环引用导致的无限循环而设立的.

11.inconsistent 是在调试模式下捕获对HT不正确的使用。

在zend/Zend_hash.h中对hashtable的定义:

typedef struct _hashtable {
uint nTableSize; // hash Bucket的大小,最小为8,以2x增长。
uint nTableMask; // nTableSize-1 , 掩码,用于根据hash值计算存储位置。
uint nNumOfElements; // hash Bucket中当前存在的元素个数,count()函数会直接返回此值
ulong nNextFreeElement; // 下一个数字索引的位置,$arr[] = "hello"时会用到
Bucket *pInternalPointer; // 当前遍历的指针(foreach比for快的原因之一)
Bucket *pListHead; // 存储数组头元素指针
Bucket *pListTail; // 存储数组尾元素指针
Bucket **arBuckets; // 存储hash数组
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
zend_bool bApplyProtection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;

 

对Bucket结构体字段的解释:

1.h是一个哈希值,未经过掩码矫正的哈希DBJ算出来的原始值。或是数字索引的数字(通过nKeyLength=0来表示是数字索引)。而对于字符串索引来说, 索引值保存在arKey中, 索引的长度保存在nKeyLength中.

2.arKey,用来记录作为哈希计算的字符串,nKeyLength是哈希字符串的长度,对于整形键值是用不到这两项的。

3.pData以及pDataPtr是实际存储数据的指针,在PHP里面他们通常是指向一个zval结构。在Bucket中,实际的数据是保存在pData指针指向的内存块中,通常这个内存块是系统另外分配的。但有一种情况例外,就是当Bucket保存 的数据是一个指针时,HashTable将不会另外请求系统分配空间来保存这个指针,而是直接将该指针保存到pDataPtr中,然后再将pData指向 本结构成员的地址。这样可以提高效率,减少内存碎片。由此我们可以看到PHP HashTable设计的精妙之处。如果Bucket中的数据不是一个指针,pDataPtr为NULL。

4.pListNext, pListLast 指定了整个数组的顺序,PHP中的遍历就是通过哈希结构体中的pListHead bucket依次遍历pListNext直到数组结束。

5.pNext和pLast 这两个指针是用来解决哈希冲突的,这个在下面哈希冲突中详细介绍,在PHP的哈希表冲突的处理采用的是拉链法也就是在每个可能冲突的键值位置拉出一个链表来存储对应的键值数据。

6.arKey 最后一个元素, 这个是flexible array技巧, 可以节省内存,和方便初始化的一种做法, 具体的参看http://blog.csdn.net/zhangboyj/article/details/6232168 (c99 柔性数组成员),博文中特意指出不能用arKey[1]的写法,这个我现在还不太懂。

在zend/Zend_hash.h中对bucket的定义:

typedef struct bucket {
/* Used for numeric indexing */
ulong h; // 对char *key进行hash后的值,数字索引的话就是索引值
uint nKeyLength; // hash关键字的长度,如果数组索引为数字,此值为0
void *pData; // 指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr
void *pDataPtr; //如果是指针数据,此值会指向真正的value,同时上面pData会指向此值
struct bucket *pListNext; // 整个hash表的下一元素
struct bucket *pListLast; // 整个哈希表该元素的上一个元素
struct bucket *pNext; // 存放在同一个hash Bucket内的下一个元素
struct bucket *pLast; // 同一个哈希bucket的上一个元素
char arKey[];
/*存储字符索引,此项必须放在最未尾,因为此处只字义了1个字节,存储的实际上是指向char *key的值,
这就意味着可以省去再赋值一次的消耗,而且,有时此值并不需要,所以同时还节省了空间。
*/
} Bucket;

php Hash Table(一) Hash Table的结构的更多相关文章

  1. OpenFlow Switch学习笔记(五)——Group Table、Meter Table及Counters

    本文主要详述OpenFlow Switch的另外两个主要组件——Group Table和Meter Table,它们在整个OpenFlow Swtich Processing中也起到了重要作用. 1. ...

  2. Hash表及hash算法的分析

    Hash表中的一些原理/概念,及根据这些原理/概念: 一.       Hash表概念 二.       Hash构造函数的方法,及适用范围 三.       Hash处理冲突方法,各自特征 四.   ...

  3. Hash::make与Hash::check

    调用方法之前要先去引用: use Illuminate\Support\Facades\Hash; 可以调用 Hash 门面上的 make 方法对存储密码进行哈希: $pwd = Hash::make ...

  4. MySQL删除大表时潜在的问题(drop table,truncate table)

    来源于:https://www.cnblogs.com/CtripDBA/p/11465315.html,侵删,纯截图,避免吸引流量之嫌 case1,删除大表时,因为清理自适应hash索引占用的内容导 ...

  5. 查询计划Hash和查询Hash

    查询计划hash和查询hash 在SQL Server 2008中引入的围绕执行计划和缓冲的新功能被称为查询计划hash和查询hash.这是使用针对查询或查询计划的算法来生成二进制hash值的二进制对 ...

  6. delete table 和 truncate table

    delete table 和 truncate table 使用delete语句删除数据的一般语法格式: delete [from] {table_name.view_name} [where< ...

  7. 14.4.3 Adaptive Hash Index 自适应hash index

    14.4.3 Adaptive Hash Index 自适应hash index 自适应hash index(AHI) 让InnoDB 执行更像内存数据库在系统使用合适的负载组合和足够的内存用于Buf ...

  8. 14.2.5.6 Adaptive Hash Indexes 自适应Hash Indexes

    14.2.5.6 Adaptive Hash Indexes 自适应Hash Indexes adaptive hash index(AHI) 让InnoDB 执行更加像在一个内存数据库里在, 在不牺 ...

  9. 转载:字符串hash总结(hash是一门优雅的暴力!)

    转载自:远航休息栈 字符串Hash总结 Hash是什么意思呢?某度翻译告诉我们: hash 英[hæʃ] 美[hæʃ]n. 剁碎的食物; #号; 蔬菜肉丁;vt. 把…弄乱; 切碎; 反复推敲; 搞糟 ...

随机推荐

  1. SpringMVC 参数传递

    使用@RequestParam 注解获取GET请求或POST请求提交的参数: 获取Cookie的值:使用@CookieValue : 根据不同的Web请求方法,映射到不同的处理方法:使用登陆页面作示例 ...

  2. Jquery-pagination.js分页处理

    首先提供pagination.js的插件源码 /** * This jQuery plugin displays pagination links inside the selected elemen ...

  3. extract()函数

    extract(),它的主要作用是将数组展开,键名作为变量名,元素值为变量值 extract($_POST);//相当于$username = $_POST['username'];//$passwo ...

  4. Linux下安装与使用本地的perl模块

    转自 http://www.cnblogs.com/xianghang123/archive/2012/08/23/2652806.html Linux下安装与使用本地的perl模块 在使用Linux ...

  5. Redis有序集合Zset(sorted set)

    zadd/zrange 127.0.0.1:6379> zadd zset01 60 v1 70 v2 80 v3 90 v4 100 v5(integer) 5127.0.0.1:6379&g ...

  6. bzoj 3223 splay模板题3

    水题...貌似理解splay怎么维护数列了... 每个点维护一个size,它的位置就是它的size,区间翻转的话可以打标记,find的时候push_down,交换左右子树. #include<i ...

  7. [U3D 添加大地、天空,用第一视角看看自己做的世界]

    1.添加大地.天空. 在下方Assets文件夹里右键,导入素材包. Terrain(地形)和Skybox(天空盒子) 2.选中左边的Terrain,在inspector里,选择中间的刷子(地形),还有 ...

  8. Alpha版本十天冲刺——Day 8

    站立式会议 会议总结 队员 今天完成 遇到的问题 明天要做 感想 鲍亮 无 同时发送图片和其它字段信息(string)到服务器,找不到好方法实现 完成发帖接口 心累,写好了一个传送文件的接口,但是后端 ...

  9. 基于UDP协议的程序设计

    使用UdpClient类进行编程 UdpClient类的使用方法 第一阶段 创建UdpClient实例 UdpClient udpClient = new UdpClient(); IPAddress ...

  10. hdu 3047–Zjnu Stadium(带权并查集)

    题目大意: 有n个人坐在zjnu体育馆里面,然后给出m个他们之间的距离, A B X, 代表B的座位比A多X. 然后求出这m个关系之间有多少个错误,所谓错误就是当前这个关系与之前的有冲突. 分析: 首 ...