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指針
需要指向新的頭結點。通過下面2個函數來加深理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//添加節點到鏈表頭
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
//新節點的next指針指向原頭結點
if (first)
first->pprev = &n->next;
//原頭結點的pprev指向新節點的next字段
h->first = n;
//first指針指向新的節點(更換了頭結點)
n->pprev = &h->first;
//此時n是鏈表的頭結點,將它的pprev指向list_head的first字段
}
//刪除節點
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
// pprev指向的是前一個節點的next指針,當該節點是頭節點時指向 hlist_head的first,兩種情況下不論該節點是一般的節點還是頭結點都可以通過這個操作刪除掉所需刪除的節點。
if (next)
next->pprev = pprev;
//使刪除節點的后一個節點的pprev指向刪除節點的前一個節點的next字段,節點成功刪除。
}
|
因本人水平有限,若文章內容存在問題,懇請指出。允許轉載,轉載請在正文明顯處注明原站地址以及原文地址,謝謝!