内核链表之HashList
哈希链表(HashList / hlist)的设计初衷是为了方便快捷的查找,为了降低Hash表中键的冲突,一般设计会将Hash桶的数量设计的比较大。Linux链表设计者认为常规的双指针头结点的双向循环链表设计对于大数量桶的Hash表过于浪费,从而设计一套适合于Hash表的只有单指针的表头。该表头只有指向首节点的指针,没有指向尾节点指针,从而在海量Hash表中能够将表头存储空间减少一半。
Hash链表这种单头指针设计缺失了对尾结点的时间复杂度的O(1)访问。同样也带来了对不同类型节点的访问问题(struct hlist_head和struct hlist_node ),所以统一使用struct hlist_node **pprev, pprev指向前一个元素的next指针的地址,不在分前一个是表头还是节点。
hlist数据节点如下:
/*
* 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; // first指针指向该hlist链表的第一个节点。
};
struct hlist_node {
struct hlist_node *next, **pprev; // next指针指向下一个节点,如果为尾结点,next指向NULL
// pprev是二级指针,指向前一个节点next指针的地址
};
hlist表头初始化,代码如下:
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
Hlist节点初始化,代码如下:
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
判断节点是否在hash链表中,代码如下:
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
判断一个hlist是否为空,代码如下:
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
hlist节点插入,代码如下:
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first; // (1)
if (first)
first->pprev = &n->next; // (2)
h->first = n; // (3)
n->pprev = &h->first; // (4)
}
如下图1所示,新节点n插入到链表头指针的下一个位置,具体代码实现和图形对照。
图 1 hlist头节点插入
hlist结点删除,代码如下:
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next; // (1)
if (next)
next->pprev = pprev; // (2)
}
如下图2所示,删除节点n,具体代码实现和图形对照
图 2 hlist节点删除
hlist链表遍历:
/*遍历一个hlist链表,不能在遍历过程中删除节点*/
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos ; pos = pos->next)
/*遍历一个hlist链表,可以在遍历过程中删除节点*/
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
hlist测试用例:
#define LISTSIZE 512
struct node_t{
unsigned char ch;
struct hlist_node hash;
};
unsigned int gethash(int c)
{
return (c % 26);
}
void main()
{
struct hlist_head hlist[LISTSIZE];
int i = 0;
unsigned int hash;
struct hlist_node *hp;
struct node_t *p;
for(hash = 0; hash < LISTSIZE; hash++)
INIT_HLIST_HEAD(&hlist[hash]);
for(i = 0; i < LISTSIZE; i++) {
p = (struct node_t *)malloc(sizeof(*p));
p->ch = i;
hash = gethash(p->ch);
hlist_add_head(&p->hash, &hlist[hash]);
}
hash = gethash('c');
hlist_for_each(hp, &hlist[hash]) {
p = hlist_entry(hp, struct node_t, hash);
printf("hlist: %x %d %p %u\n", p->ch, p->ch, p, hash);
}
}