原理
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 void INIT_HLIST_NODE(struct hlist_node *h)
- {
- h->next = NULL;
- h->pprev = NULL;
- }
3、节点是否存在
- /* 判断节点h是否在散列表中
- * 若不在,则返回1
- * 否则,返回0
- */
- static inline int hlist_unhashed(const struct hlist_node *h)
- {
- return !h->pprev;
- }
4、判空
- /* 判断散列链表h是否为空
- * 为空,则返回1
- * 否则,返回0
- */
- static inline int hlist_empty(const struct hlist_head *h)
- {
- return !h->first;
- }
5、删除节点
- static inline void __hlist_del(struct hlist_node *n)
- {
- struct hlist_node *next = n->next;
- struct hlist_node **pprev = n->pprev;
- *pprev = next; /* 将节点n的前一个节点的next域设置为n的下一个节点的地址 */
- if (next)
- /* 将节点n的下一个节点的pprev域设置为节点n的前一个节点的next域的地址 */
- next->pprev = pprev;
- }
- /* 将节点n从散列表中删除,再次访问该节点将会产生页错误 */
- static inline void hlist_del(struct hlist_node *n)
- {
- __hlist_del(n);
- n->next = LIST_POISON1;
- n->pprev = LIST_POISON2;
- }
- /* 将节点n从散列表中删除,然后将其初始化为空节点 */
- static inline void hlist_del_init(struct hlist_node *n)
- {
- if (!hlist_unhashed(n)) { /* 判断节点是否存在散列链表中 */
- __hlist_del(n);
- INIT_HLIST_NODE(n);
- }
- }
6、插入节点
- /* 将节点n插入到头节点h之后*/
- static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
- {
- struct hlist_node *first = h->first;
- n->next = first;
- if (first)
- first->pprev = &n->next;
- h->first = n;
- n->pprev = &h->first;
- }
- /* 将节点n插入到节点next之前,next在散列表中 */
- /* next must be != NULL */
- static inline void hlist_add_before(struct hlist_node *n,
- struct hlist_node *next)
- {
- n->pprev = next->pprev;
- n->next = next;
- next->pprev = &n->next;
- *(n->pprev) = n;
- }
- /* 将节点next插入到节点n之后,n在散列表中 */
- static inline void hlist_add_after(struct hlist_node *n,
- struct hlist_node *next)
- {
- next->next = n->next;
- n->next = next;
- next->pprev = &n->next;
- if(next->next)
- next->next->pprev = &next->next;
- }
7、移动散列链表
- /*
- * Move a list from one list head to another. Fixup the pprev
- * reference of the first entry if it exists.
- */
- /* 将一个散列链表的头节点用new节点代替,将以前的头节点old删除 */
- static inline void hlist_move_list(struct hlist_head *old,
- struct hlist_head *new)
- {
- new->first = old->first;
- if (new->first)
- new->first->pprev = &new->first;
- old->first = NULL;
- }
8、遍历
1)一个重要的宏
- /* 通过成员指针获得整个结构体的指针
- *
- * ptr: 指向该数据结构中hlist_head/hlist_node成员的指针
- * type: 该数据结构的类型
- * member: 该数据结构类型定义中hlist_head/hlist_node成员的变量名
- */
- #define hlist_entry(ptr, type, member) container_of(ptr,type,member)
2)从头节点开始遍历
- /* 从头节点开始遍历散列链表
- * 遍历时,不可删除pos节点
- *
- * pos: 辅助指针(struct hlist_node *类型),用于链表遍历
- * head: 散列链表头节点(struct hlist_head *类型)
- */
- #define hlist_for_each(pos, head) \
- for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
- pos = pos->next)
- /* 从头节点开始遍历散列链表
- * 遍历时,会暂存pos节点的下一个节点,可以删除pos节点
- *
- * pos: 辅助指针(struct hlist_node *类型),用于链表遍历
- * n: 与pos同类的指针,用于暂存pos节点的下一个节点
- * head: 散列链表头节点(struct hlist_head *类型)
- */
- #define hlist_for_each_safe(pos, n, head) \
- /* pos && ({ n = pos->next; 1; }):
- * 若pos为真,则会执行后一条复合语句,取得pos节点的下一个节点
- * 该复合语句永远为真
- */
- for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
- pos = n)
- /**
- * hlist_for_each_entry - iterate over list of given type
- * @tpos: the type * to use as a loop cursor.
- * @pos: the &struct hlist_node to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the hlist_node within the struct.
- */
- /* 从链表头开始遍历散列链表
- *
- * tpos: 包含struct hlist_node型变量的数据类型指针,一般为结构体
- * pos: 辅助指针,struct hlist_node *类型
- * head: 散列链表头节点
- * member: 包含在tpos数据类型中的struct hlist_node成员名称
- */
- #define hlist_for_each_entry(tpos, pos, head, member) \
- for (pos = (head)->first; \
- pos && ({ prefetch(pos->next); 1;}) && \
- ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
- pos = pos->next)
- /**
- * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
- * @tpos: the type * to use as a loop cursor.
- * @pos: the &struct hlist_node to use as a loop cursor.
- * @n: another &struct hlist_node to use as temporary storage
- * @head: the head for your list.
- * @member: the name of the hlist_node within the struct.
- */
- /* 从链表头开始遍历散列链表
- * 遍历时,会暂存pos节点的下一个节点
- *
- * tpos: 包含struct hlist_node型变量的数据类型指针,一般为结构体
- * pos: 辅助指针,struct hlist_node *类型
- * n: 辅助指针,struct hlist_node *类型,暂存pos节点的下一个节点
- * head: 散列链表头节点
- * member: 包含在tpos数据类型中的struct hlist_node成员名称
- */
- #define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
- for (pos = (head)->first; \
- pos && ({ n = pos->next; 1; }) && \
- ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
- pos = n)
- /**
- * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
- * @tpos: the type * to use as a loop cursor.
- * @pos: the &struct hlist_node to use as a loop cursor.
- * @member: the name of the hlist_node within the struct.
- */
- /* 从当前节点的下一个节点开始遍历散列链表
- *
- * tpos: 包含struct hlist_node型变量的数据类型指针,一般为结构体
- * pos: struct hlist_node *类型,从pos节点的下一个节点开始遍历
- * member: 包含在tpos数据类型中的struct hlist_node成员名称
- */
- #define hlist_for_each_entry_continue(tpos, pos, member) \
- for (pos = (pos)->next; \
- pos && ({ prefetch(pos->next); 1;}) && \
- ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
- pos = pos->next)
- /**
- * hlist_for_each_entry_from - iterate over a hlist continuing from current point
- * @tpos: the type * to use as a loop cursor.
- * @pos: the &struct hlist_node to use as a loop cursor.
- * @member: the name of the hlist_node within the struct.
- */
- /* 从当前节点开始遍历散列链表
- *
- * tpos: 包含struct hlist_node型变量的数据类型指针,一般为结构体
- * pos: struct hlist_node *类型,从pos节点开始遍历
- * member: 包含在tpos数据类型中的struct hlist_node成员名称
- */
- #define hlist_for_each_entry_from(tpos, pos, member) \
- for (; pos && ({ prefetch(pos->next); 1;}) && \
- ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
- pos = pos->next)
内核版本:2.6.32
参考:
1、《数据结构(C语言版)[严蔚敏]》