关于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. 【BZOJ 1178】【APIO 2009】CONVENTION会议中心

    http://www.lydsy.com/JudgeOnline/problem.php?id=1178 这道题想了好久没想明白,倍增数组通过看题解很快就明白了,但是一小段区间内应有的最多线段数一直不 ...

  2. thinkphp全站静态页实现方法

    1:在根目录下的全局index.php中加下面这行: define('HTML_PATH', './htm');//生成静态页面的文件位置 2:在项目的配置文件config.php中加下面这行: 'H ...

  3. 解决npm install缓慢

    http://npm.taobao.org/ 使用淘宝提供的cnpm替代npm

  4. 【HDU 5818多校】Joint Stacks

    用两个栈模拟,并保存每个点的时间戳.每次合并的时候记录合并时的时间戳mcnt和此时的topa和topb记做ta.tb. 每次pop的时候,如果栈的top的时间戳大于mcnt,则普通地pop,否则就在两 ...

  5. Java面试知识的认知描述

    连接池:同时操作连接数据库,创建一个连接池,池子里创建一万个数据库链接.根据系统运行情况关闭链接.高峰期链接达到最大MAX进行排队,对在用的链接实现失效时间.有几种:tomcat请求服务器(serve ...

  6. 58. Android一些开发习惯总结

    作者:漫步 链接:https://www.zhihu.com/question/27227425/answer/35973793 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  7. 【caffe】create_cifar10.sh在windows下解决方案

    @tags caffe python windows下配置caffe后,create_cifar10.sh无法执行,因为是shell脚本.那就看懂脚本意思,用python重写一个: # create_ ...

  8. Stream篇

    本文转自: http://www.cnblogs.com/JimmyZheng/archive/2012/03/17/2402814.html 什么是Stream? 什么是字节序列? Stream的构 ...

  9. 禁用Resharper长代码自动换行的解决办法

    Resharper Options=> C#=> Formatting Style=> Line Breaks and Wrapping=> Right margin(colu ...

  10. c/c++中#和##链接符号的用法

    #include <stdio.h> #include <stdlib.h> /* 英语原文: In function-like macros, a # operator be ...