一、Hash表的基本定义
1.1 Hash的概念
散列表(Hash table,也叫哈希表),是一种数据结构,可以用于存储Key-Value键值对。也就是说,通过Key来映射到具体的Value。通常用于查找。将Key映射到Value的函数叫做Hash函数,而存储Key-Value的表叫做Hash表。Hasn表常用数组来存储。
1.2 常用的Hash函数
1.3 常用的处理碰撞的方法
如果说存储空间是无线的,那只要定义一个与需要存储的键值对数量等同的数组即可,这样每一个元素都有对应的存储空间,但实际上使用hash表时,数组通常远小于元素数量。这样不同的key值通过hash函数后可能映射到相同的位置,这样就必须有处理碰撞的方法。
(1)直接定址法
(2)数字分析法
(3)平方取中法
(4)折叠法
(5)随机数法
(6)除留余数法
(7)拉链法
hash表与数据结构的相关内容这里不多描述,可以参考相关的数据结构书籍。
linux内核的hash表用的是拉链法
二、Linux内核中的Hash表
2. 1 定义
数据结构定义于:include/linux/types.h
接口定义于:inxlude/linux/list.h
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
(1)hlist_head是哈希链表的表头,会定义一个数组作为哈希表,用于存放hlist_head。hlist_head也就是每个链表的头结点。hlist_head只有一个first成员,指向hlist链表的第一个节点,也就是说链表是双向链表而不是双向循环链表。hlist_head只有一个first成员的原因是,为了节省空间。因为hash数组存放的是hlist_head,去掉一个成员可以节省一半的空间,这样可以存放更多的hlist_head,减少碰撞的几率,加快查表的速度。
(2)hlist_node是哈希链表的节点,哈希到同一个表头的节点会链接到对应的hlist_head后面。hlist_node有两个成员,分别是next和pprev。next指向下一个hist_node节点,倘若改结点是链表的最后一个节点,next则指向NULL。pprev是一个二级指针,它指向前一个节点的next指针。 为了能统一地修改表头的first指针,即表头的first指针必须修改指向新插入的结点,hlist就设计了pprev。list结点的pprev不再 是指向前一个结点的指针,而是指向前一个节点(可能是表头)中的next(对于表头则是first)指针,从而在表头插入的操作中可以通过一致的 node->pprev访问和修改前结点的next(或first)指针。还解决了数据结构不一致,hlist_node巧妙的将pprev指向上一个节点的next指针的地址,由于hlist_head和hlist_node指向的下一个节点的指针类型相同,就解决了通用性。如下图
2.2 接口
2.2.1 链表头hlist_head的初始化:
主要是对成员初始化为NULL
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
2.2.2 节点的初始化
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
2.2.3 判断函数
判断一个节点是否在hash表中。
判断hash链表是否空
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
static inline bool hlist_fake(struct hlist_node *h)
{
return h->pprev == &h->next;
}
2.2.4 操作函数
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
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;
}
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;
}
static inline void hlist_add_behind(struct hlist_node *n,
struct hlist_node *prev)
{
n->next = prev->next;
prev->next = n;
n->pprev = &prev->next;
if (n->next)
n->next->pprev = &n->next;
}
/* after that we'll appear to be on some hlist and hlist_del will work */
static inline void hlist_add_fake(struct hlist_node *n)
{
n->pprev = &n->next;
}
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;
}