源码位置:
nginx/src/core/ngx_hash.h
nginx/src/core/ngx_hash.c
(一)数组与hash表
从查询的角度来看,数组根据索引值的查询速度很快快。
原因在于数组内元素的位置是基于数组起始位置的绝对位置,而且数组的存储空间是连续的,可以根据下标直接操作指针跳转。
虽然数组的查询速度很快,但是数组的索引值必须是数值,这就很讨厌了。 因为很多情况下,索引值并不是数字,而是字符串什么的。比如用名字来索引一个人。
解决这个问题的一个很容易的办法就是给每个人安排一个学号(先不考虑重名的情况),那么,在实际存储时,按照学号为索引值的数组来存储对应的信息;在查询时,只需要知道名字,就可以得到名字对应的学号,根据学号可以直接从数组中取出信息。
这个解决方法中有两个主要部分:
建立从名字到学号的对应关系;
建立以学号为索引值的数组;
从名字到学号的对应关系可以抽象成从字符串到数值的对应关系,这种对应关系,在数学上表示就是f(k)。其中k表示一个字符串(索引关键字),函数f表示从字符串到数值的对应关系,f(k)表示k经过f映射得到的值。
只要有了f(k),那么将f(k)作为数组的下标即可获取k所对应的信息。即:
k——>f(k)——->info[f(k)]
其中,从k到f(k)的映射函数称为哈希函数,数组info[]称为哈希(hash)表。
(二)hash表的问题及解决方法
理想是丰满的,现实是骨感的。hash表在建立时最关键之处在于找到合适的哈希函数,使得:
k与f(k)之间是一一映射的。即,保证给定对于k存在唯一的f(k)与之对应,同时对于f(k)存在唯一的k与之对应。
f(k)的集合是连续的。即,对于数组info[]而言,不存在数组项为空的情况,可以更加充分利用资源。
可惜,满足上述条件的哈希函数非常困难。
现在使用的各种哈希函数基本上只能保证较小概率出现两个不同的k其f(k)相同的情况。
基本不能保证f(k)的集合是连续的。
因为f(k)的集合不是连续的,所以哈希表也被称为散列表,哈希函数也被称为散列函数。
而出现两个k值对应的f(k)相同的情况,称为哈希冲突。
解决哈希冲突常见的办法
出现散列情况表示可能浪费一点资源,这是可以接受的。但是出现冲突表示会发生信息覆盖,这是错误,不能接受。所以,必须解决哈希冲突。
解决哈希冲突的常见的方法有: 1) 开放地址法;2)再哈希法;3)链地址法;
(三)Nginx中hash表
Nginx的哈希表在内存上大概是长这个样子的:
根据哈希表的概念可知:哈希表本身就是一个数组,因此,是一块连续的内存空间。
bucket是一个二重指针因为这样当算出当算出的hash值一样时可以解决冲突
这个用来代替链表的数组还有个名字叫hash桶,所以,会在Nginx源码中看到buckets这样的命名。
(四)数据结构
//该结构表示散列表中的槽
typedef struct {
/*指向用户自定义元素数据*/
void *value;
/*元素关键字的长度*/
u_short len;
/*元素关键字首地址*/
u_char name[1];
} ngx_hash_elt_t;
//散列表结构体
typedef struct {
ngx_hash_elt_t **buckets;
//散列表中槽的个数
ngx_uint_t size;
} ngx_hash_t;
ngx_hash_t
是基本散列表,此外nginx还提供支持通配符的散列表。
//表示前置或者后置通配符
typedef struct {
//基本散列表
ngx_hash_t hash;
//value指针可以指向用户数据
void *value;
} ngx_hash_wildcard_t;
实际使用的时候,即可能精准匹配,也可能前缀/后缀匹配,因此nginx封装了一个包含这三种情况的结构体:
typedef struct {
//精准匹配的散列表
ngx_hash_t hash;
//查询前置通配符的散列表
ngx_hash_wildcard_t *wc_head;
//查询后置通配符的散列表
ngx_hash_wildcard_t *wc_tail;
} ngx_hash_combined_t;