hash表的整体设计要素
先回顾一下hash表的设计的几个要素:
- hash函数的构造-散列函数
- 冲突处理方式
- 装填因子大小的选择。装填因子 a=n / m。其中m为hash表的bucket个数;(n为关键字的个数。装填因子越大,产生hash冲突就严重。)
hash函数构造方法
经典的字符串hash构造算法主要以下几个:
- BKDRHash
- APHash
- DJBHash
- JSHash
- RSHash
- SDBMHash
- PJWHash
- ELFHash
nginx中默认的hash算法采用的是BKDR hash算法
/* BKDR算法推导公式, 将任意长度的字符转换成数字 */
#define ngx_hash(key, c) ((ngx_uint_t) key * 31 + c)
/* 字符转换成小写,字母的大小写相差32 */
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
/* 上次hash的结果作为当前key值,其目的是为了扩大key值冲突的间隔,减少冲突 */
ngx_uint_t
ngx_hash_key(u_char *data, size_t len)
{
ngx_uint_t i, key;
key = 0;
for (i = 0; i < len; i++) {
key = ngx_hash(key, data[i]); /* key * 31 + c test*/
}
return key;
}
/* 将字符串小写后,再使用BKDR算法将任意长度字符串映射成整型*/
ngx_uint_t
ngx_hash_key_lc(u_char *data, size_t len)
{
ngx_uint_t i, key;
key = 0;
for (i = 0; i < len; i++) {
key = ngx_hash(key, ngx_tolower(data[i]));
}
return key;
}
BDKhash算法推理参考:https://blog.csdn.net/wanglx_/article/details/40400693
常见的冲突处理方法
- 开放定(寻)址法:冲突产生时候,可存放新项的空闲地址可以被同义词和非同义词都使用。(hash值相同的关键词称为同义词)
- 线性探测法:顺序查看表中下一个单元直到产生一个空闲单元。
- 容易导致大量元素堆积,降低查找效率。
- 平方探测法:计算hash值地址增量进行
- 可以避免堆积问题,缺点是不能探测到散列表上所有单元,但至少能探测到一半单元
- 再离散法:使用两个hash函数,第一个发生冲突时候,用第二个hash函数计算地址增量
- 线性探测法:顺序查看表中下一个单元直到产生一个空闲单元。
- 拉链法: 一般拉链法用的比较多,主要插入删除比较方便。
- 简单的讲就是将hash值一样的同义词用链表连接起来
- 优点:插入和删除比较方便
- 缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度
nginx当中使用的是连续非空槽存储碰撞元素的方法—>线性探测法:冲突发生时候,顺序查看表的下一个单元找到一个空闲的表单元
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
ngx_uint_t i;
ngx_hash_elt_t *elt;
/* 先获取对应key的hash值位置 */
elt = hash->buckets[key % hash->size];
if (elt == NULL) {
return NULL;
}
while (elt->value) {
if (len != (size_t) elt->len) {
goto next;
}
for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
}
return elt->value;
next:
/* 发现冲突后,从当前位置依次往后面找空闲的表单元存放 */
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
}
return NULL;
}
nginx中hash表的应用场景
nginx实现了两类hash结构,
- 一类是key中包含通配符的ngx_hash_wildcard_t,这里的通配符指*号。(可以利用正则表达式匹配,主要用来虚拟主机的匹配)
- 另一类则是key中不包含通配符的ngx_hash_t
以上分析的是nginx中不含通配符的hash表结构,nginx当中hash表的大小是静态固定的,如果数据项大的话,可以改成可变大小的hash表结构
含有带通配符的hash表设计可以参考:https://www.cnblogs.com/chengxuyuancc/p/3782808.html