原理
1、概念
根据设定的哈希函数H(key)和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表或散列表,这一映像过程称为哈希造表或散列,所得存储位置称哈希地址或散列地址。
2、哈希函数的构造方法
1)直接定址法
2)数字分析法
3)平方取中法
4)折叠法
5)除留余数法
6)随机数法
实际工作中需视不同的情况采用不同的哈希函数。通常,考虑的因素有:
1)计算哈希函数所需时间(包括硬件指令的因素)
2)关键字的长度
3)哈希表的大小
4)关键字的分布情况
5)记录的查找频率
3、处理冲突的方法
1)开放定址法
2)再哈希法
3)链地址法
4)建立一个公共缓冲区
4、哈希表的查找
在哈希表上进行查找的过程和哈希造表的过程基本一致。给定K值,根据造表时设定的哈希函数求得哈希地址,若表中此位置上没有记录,则查找不成功;否则比较关键字,若和给定值相等,则查找成功;否则根据造表时设定的处理冲突的方法找“下一地址”,直到哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。
5、装填因子
哈希表的装填因子定义为:
a = 表中填入的记录数 / 哈希表的长度
a标志哈希表的装满程度。直观地看,a越小,发生冲突的可能性就越小;反之,a越大,表中已填入的记录越多,再填记录时,发生冲突的可能性就越大,则查找时,给定值需与之比较的关键字的个数也就越多。
linux内核中的散列表
在处理记录的冲突时,linux内核使用的方法为链地址法,即将所有关键字为同义词的记录存储在同一线性链表中。
下面分析下,内核针对散列链表实现了哪些操作(所有实现均在源文件inlcude/linux/list.h中)。
1、定义
- /*
- * Double linked lists with a single pointer list head.
- * Mostly useful for hash tables where the two pointer list head is
- * too wasteful.
- * You lose the ability to access the tail in O(1).
- */
- /* 散列表头结点,即散列链表 */
- struct hlist_head {
- struct hlist_node *first; /* 指向hlist链表的第一个节点 */
- };
- /* 散列表节点 */
- struct hlist_node {
- /* next: 指向下一个节点
- * pprev: 指向前一个节点的next域,则*pprev就代表前一个节点的下一个节点的地址(即当前节点)
- * 当前节点: pprev == 前一个节点: &next
- * 当前节点: *pprev == 前一个节点: next
- * 当前节点: **pprev == 前一个节点: *next
- */
- struct hlist_node *next, **pprev;
- };
2、声明与初始化
- /* 散列表头节点的声明与初始化 */
- #define HLIST_HEAD_INIT { .first = NULL }
- #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } /* 静态初始化 */
- #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) /* 动态初始化 */
- /* 散列表节点的初始化
- * 该函数一般用在删除节点之后对节点的操作中
- */
- static inline