哈希表
哈希表是一个基础的数据结构,中所周知,数组的随即访问效率是最高的,原因的是数组的随即访问可以通过索引直接定位到数据的实际地址,而无需遍历数组实现,这正是哈希表的思想,而数组也可以看成是索引为数字的特殊哈希表
哈希表实现了给定索引,直接计算出被索引数据存储地址的功能,这个过程是通过两次映射实现的
1.给定索引数据K,通过一个映射函数f(k)计算出实际的key值
2.通过计算出的key值在info数组中直接得到偏移
即:
k-------> f(k) ------->info[f(k)]
上诉过程,f(k)就是哈希函数,Info数组就是哈希表
在理想状态下,k与key是一 一对应的,且info数组中不存在空元素,而实际情况往往是无法做到的
两个不同的K经过f(k)得到了相同的key的情况就称为哈希碰撞/冲突,解决碰撞有三种思路:
1.开放地址法
2.再哈希法
3.链地址法
nginx采用的是链地址法,通过分桶的方式解决冲突的情况
在逻辑上,Nginx的哈希表是一个二维数组,这个数组的大小受两个因素影响;桶的大小和桶的个数
nginx的哈希算法
需要注意的是nginx的哈希表一经初始化就不可更改,既不能增加元素,也不能删除元素
正是因为这个特性,所以可以预先知道所有元素的偏移,因此采用链地址法可以有效解决冲突问题,同时使用数组来代替链表解决冲突则是更优的选择
nginx哈希表结构
上面提到了,nginx的哈希表实际上是一个二维数组,他采用开放地址法通过分桶解决冲突问题,因此,nginx采用了两层结构实现哈希表
// struct ngx_hash_t
//哈希表结构
typedef struct {
ngx_hash_elt_t **buckets; //哈希桶数组
ngx_uint_t size; //哈希桶数组长度
}ngx_hash_t;
buckets 是一个指向ngx_hash_elt_t数组的指针,由他构成了一个ngx_hash_elt_t为元素的二维数组,每一维的长度则由他指向的ngx_hash_elt_t数组来决定
// struct ngx_hash_elt_t
//哈希桶结构
typedef struct {
void *value; //为null代表该元素为当前bucket最尾元素
u_short len; //key长度
u_char name[1]; //key的值
}ngx_hash_elt_t;
哈希表所有的key ,value则存储在哈希表索引结构ngx_hash_key_t中,用于在哈希表创建时提供数据
//struct ngx_hash_key_t
//哈希索引结构
typedef struct {
ngx_str_t key; //索引
ngx_uint_t key_hash ; //索引指向的哈希值
void *value ; //内容
}ngx_hash_key_t;
哈希表创建结构 -- ngx_hash_init_t
//哈希表创建时指定的哈希函数
typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data,size_t len);
// struct ngx_hash_init_t
//哈希表创建限制结构
typedef struct {
ngx_hash_t *hash; //用于管理哈希表的结构体
ngx_hash_key_pt key; // 哈希函数
ngx_uint_t max_size; //哈希桶个数最大值
ngx_uint_t bucket_size; //哈希桶大小
char *name; //哈希表名字
ngx_pool_t *pool; //哈希表所使用的内存池
ngx_pool_t *temp_pool; //估算临时使用的内存池
}ngx_hash_init_t;
这个结构中不仅定义了即将要创建的哈希表桶的最大个数和桶大小的限制,还有指定的哈希函数、用于分配哈希表的内存池等创建哈希表所必须的属性资源
哈希表的创建 ---ngx_hash_init
这个函数通过预先已经存储了所有的数据ngx_hash_key_t 结构和存储限制条件ngx_hash_init_t结构创建了hinit->hash的ngx_hash_t结构
在这个函数中首先进行了限制条件的校验,保证空间足够,然后通过循环,计算了每个数据的大小和偏移,最终将哈希表创建在内存池中一块连续的内存中
哈希表的查询 -- ngx_hash_find
哈希表的查询,首先通过上面说的key % size 得到桶数组序号,然后通过buckets指针获取桶数组首地址,然后遍历对应的桶数组,最终返回查询结果