哈希表最关键的几个方面有:

  1. 通过key访问(通过哈希函数计算出key)
  2. 映射到数据结构中(哈希表本身的存储结构)
  3. 映射的处理(冲突或者碰撞检测和处理函数)

理解PHP的哈希算法

一般来说对于整形索引进行哈希我们很容易想到的是取模运算,比如array(1=>'a', 2=>'b', 3=>'c'),这类我们可以使用index%3来哈希,不过PHP数组的下标还有更灵活的array('a'='c', 'b'=>'d'),此时选择什么哈希函数?答案是DJBX33A算法。

PS:DJBX33A算法,也就是time33算法,是APR默认哈希算法,php, apache, perl, bsddb也都使用time33哈希。对于33这个数,DJB注释中是说,1到256之间的所有奇数,都能达到一个可接受的哈希分布,平均分布大概是86%。而其中33,17,31,63,127,129这几个数在面对大量的哈希运算时有一个更大的优势,就是这些数字能将乘法用位运算配合加减法替换,这样运算速度会更高。gcc编译器开启优化后会自动将乘法转换为位运算。

下面就是这个哈希函数的具体代码实现:

更详细的解释看鸟哥:http://www.laruence.com/2009/07/23/994.html

static inline ulong zend_inline_hash_func(char *arKey, uint nKeyLength){       register ulong hash = 5381;     /* variant with the hash unrolled eight times */    for (; nKeyLength >= 8; nKeyLength -= 8) {        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;        hash = ((hash << 5) + hash) + *arKey++;    }switch (nKeyLength) {        case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */        case 1: hash = ((hash << 5) + hash) + *arKey++; break;        case 0: break;EMPTY_SWITCH_DEFAULT_CASE()    }return hash;
}

nTableMask

PHP哈希表最小容量是8(2^3),最大容量是0x80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。

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

 hash  |   193491849 |   0b1011100010000111001110001001& mask  | &        63 | & 0b0000000000000000000000111111
---------------------------------------------------------= index | =         9 | = 0b0000000000000000000000001001

具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{uint i = 3;Bucket **tmp;SET_INCONSISTENT(HT_OK);//长度向2的整数次幂圆整if (nSize >= 0x80000000) {/* prevent overflow */ht->nTableSize = 0x80000000;} else {while ((1U << i) < nSize) {i++;}ht->nTableSize = 1 << i;}ht->nTableMask = ht->nTableSize - 1;/*此处省略若干代码…*/return SUCCESS;
}

Zend HashTable的哈希算法比较简单:

hash(key)=key & nTableMask

即简单将数据的原始key与HashTable的nTableMask进行按位与即可。

如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与。

hash(strkey)=time33(strkey) & nTableMask

下面是Zend源码中查找哈希表的代码:

ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
{uint nIndex;Bucket *p;IS_CONSISTENT(ht);nIndex = h & ht->nTableMask;p = ht->arBuckets[nIndex];while (p != NULL) {if ((p->h == h) && (p->nKeyLength == 0)) {*pData = p->pData;return SUCCESS;}p = p->pNext;}return FAILURE;
}ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
{ulong h;uint nIndex;Bucket *p;IS_CONSISTENT(ht);h = zend_inline_hash_func(arKey, nKeyLength);nIndex = h & ht->nTableMask;p = ht->arBuckets[nIndex];while (p != NULL) {if ((p->h == h) && (p->nKeyLength == nKeyLength)) {if (!memcmp(p->arKey, arKey, nKeyLength)) {*pData = p->pData;return SUCCESS;}}p = p->pNext;}return FAILURE;
}

其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法。

哈希冲突的处理

关于哈希冲突,PHP的实现是通过拉链法实现的,当键值被哈希到同一个槽位(bucket)就是发生了冲突,这时候会从bucket拉出一个链表把冲突的元素顺序链接起来。

关于那两对指针,国外有网站上搞错了,这里把检测哈希冲突的PHP函数贴出来,pNext指针的作用就一目了然了。

ZEND_API int zend_hash_exists(const HashTable *ht, const char *arKey, uint nKeyLength)
{ulong h;uint nIndex;Bucket *p;IS_CONSISTENT(ht);h = zend_inline_hash_func(arKey, nKeyLength);nIndex = h & ht->nTableMask;p = ht->arBuckets[nIndex];while (p != NULL) {if (p->arKey == arKey ||((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) {return 1;}p = p->pNext;}return 0;
}

转载于:https://www.cnblogs.com/leezhxing/p/4823674.html

php Hash Table(二) Hash函数相关推荐

  1. php Hash Table(四) Hash Table添加和更新元素

    HashTable添加和更新的函数: 有4个主要的函数用于插入和更新HashTable的数据: int zend_hash_add(HashTable *ht, char *arKey, uint n ...

  2. 数据结构(55) 散列表(哈希表,hash table,hash map)

    目录 1.散列表的基本概念 2.散列函数的构造方法 3.常用的散列函数 3.1.直接定址法 3.2.除留余数法 3.3.数字分析法 3.4.平方取中法 3.5.乘法哈希法(The Multiplica ...

  3. C++利用二次探查实现存储机制hash table的算法(附完整源码)

    C++利用二次探查实现存储机制hash table的算法 C++利用二次探查实现存储机制hash table的算法完整源码(定义,实现,main函数测试) C++利用二次探查实现存储机制hash ta ...

  4. hash table(开放寻址法-二次探查实现的哈希表)

    hash table(开放寻址法-二次探查实现的哈希表) #ifndef C11LEARN_HASHQUADRATIC_H #define C11LEARN_HASHQUADRATIC_H #incl ...

  5. hash function/ hash table 背后的数学基础(二)

    hash function/ hash table 背后的数学基础 perfect hashing(完美哈希) 给定 nn 个键,构建一个静态表(static hash table,也即没有插入和删除 ...

  6. 数据结构基础-Hash Table详解

    理解Hash 哈希表(hash table)是从一个集合A到另一个集合B的映射(mapping). 映射是一种对应关系,而且集合A的某个元素只能对应集合B中的一个元素.但反过来,集合B中的一个元素可能 ...

  7. 十、散列表(Hash Table)

    一.概述 散列表(Hash Table),也称"哈希表"或者"Hash 表" 1.相关概念 原始数据叫作键(键值)或关键字(key): 将原始数据转化为数组下标 ...

  8. 数据结构学习笔记(七):哈希表(Hash Table)

    目录 1 哈希表的含义与结构特点 1.1 哈希(Hash)即无序 1.2 从数组看哈希表的结构特点 2 哈希函数(Hash Function)与哈希冲突(Hash Collision) 2.1 哈希函 ...

  9. 数据结构-Hash总结(二)

    转载:http://blog.csdn.net/liufei_learning/article/details/19220391 理解Hash 哈希表(hash table)是从一个集合A到另一个集合 ...

  10. 【STL学习】自己动手C++编程实现hash table(散列表)

    SGI STL中散列表采用链接法解决冲突.结构中维护了一个vector,vector中每一个元素称为一个桶(bucket),它包含的是一个链表的第一个节点. 下面代码展示了自己编程实现的hash ta ...

最新文章

  1. springboot webservice接口调用_springboot远程调用dubbo服务接口
  2. Matlab函数解释:colormap
  3. Leetcode-区域和检索 - 数组不可变(303)
  4. 设计师电脑推荐笔记本_笔记本电脑选购推荐全攻略
  5. python数据分析软件_Python数据分析工具
  6. Spring@懒惰注释
  7. Go获取命令行参数及信号量处理
  8. JavaGUI实现科学计算器
  9. 合并两个有序数组js
  10. 浅谈国内软件信息化项目项目立项管理办法
  11. Python functools模块之cmp_to_key
  12. PCAN二次开发,用MFC发送一帧CAN消息
  13. ABAQUS软件实训(四):Mesh模块之六面体网格划分技巧
  14. input file 上传图片及压缩
  15. Web前端知识CSS(响应式设计)
  16. 百余门店闭门谢客,韩妆如何败给了国潮?
  17. 141. 环形链表(java实现)--2种解法(双指针,hahs)LeetCode
  18. 机器学习 | 逻辑回归
  19. 网站访问及DNS原理
  20. RTOS系统CPU使用率和任务堆栈空间统计方法

热门文章

  1. TE飞到对象完成事件
  2. Linux paste命令
  3. RTMP vs RTMFP
  4. [Android]OpenGL绘制2D几何图形
  5. [Material Design] MaterialButton 效果进阶 动画自动移动进行对齐效果
  6. Fiddler中常用的方法
  7. uniapp实现的购物列表左右联动功能
  8. nodemon运行 提示错误:无法加载文件 C:\Users\gxf\AppData\Roaming\npm\nodemon.ps1,因为在此系统上禁止运行脚本。
  9. 前端页面怎么办啊一条长的信息换行展示_前端入门!不容错过!HTML基本标签知识大盘点...
  10. python显示文字框_python如何使用文本框