转自https://www.cnblogs.com/zafu/p/7412424.html
hlist_head
和hlist_node
用于散列表,分表表示列表头(数组中的一项)和列表头所在双向链表中的某项,两者结构如下:
1 2 3 | struct hlist_head { struct hlist_node *first; }; |
1 2 3 | struct hlist_node { struct hlist_node *next, **pprev; }; |
在内核中的普通双向链表基本上都是通过list_head
实现的:
1 2 3 | struct list_head { struct list_head *next, *prev; }; |
list_head
很好理解,但是hlist_head
和hlist_node
为何要这样设计呢?
先看下hlist_head
和hlist_node
的示意图:
hash_table
为散列表(数组),其中的元素类型为struct hlist_head
。以hlist_head
为链表头的链表,其中的节点hash值
是相同的(也叫冲突)。first指针
指向链表中的节点①,然后节点①的pprev指针
指向hlist_head
中的first
,节点①的next指针
指向节点②。以此类推。
hash_table
的列表头仅存放一个指针,也就是first指针,指向的是对应链表的头结点,没有tail指针也就是指向链表尾节点的指针,这样的考虑是为了节省空间——尤其在hash bucket
(数组size)很大的情况下可以节省一半的指针空间。
为什么pprev
是一个指向指针的指针呢?按照这个设计,我们如果想要得到尾节点,必须遍历整个链表,可如果是一个指向节点的指针,那么头结点现在的pprev
便可以直接指向尾节点,也就是list_head
的做法。
对于散列表来说,一般发生冲突的情况并不多(除非hash设计出现了问题),所以一个链表中的元素数量比较有限,遍历的劣势基本可以忽略。
在删除链表头结点的时候,pprev
这个设计无需判断删除的节点是否为头结点。如果是普通双向链表的设计,那么删除头结点之后,hlist_head
中的first指针
需要指向新的头结点。