核心中的定義:
struct hlist_head {
structhlist_node *first;
};
struct hlist_node {
structhlist_node *next, **pprev;
};
這個資料結構與一般的hash-list資料結構定義有以下的區別:
1)首先,hash的前端節點僅存放一個指標,也就是first指標,指向的是list的頭結點,沒有tail指標也就是指向list尾節點的指標,這樣的考慮是為了節省空間的——尤其在hashbucket很大的情況下可以節省一半的指標空間.
2)list的節點有兩個指標,但是需要注意的是pprev是指標的指標,它指向的是前一個節點的next指標;其中首元素的pprev指向鏈表頭的fist欄位,末元素的next為NULL.(見).
現在疑問來了:為什麼pprev不是prev也就是一個指標,用於簡單的指向list的前一個指標呢?這樣即使對於first而言,它可以將prev指標指向list的尾結點.
主要是基於以下幾個考慮:
1)hash-list中的list一般元素不多(如果太多了一般是設計出現了問題),即使遍曆也不需要太大的代價,同時需要得到尾結點的需求也不多.
2)如果對於一般節點而言,prev指向的是前一個指標,而對於first也就是hash的第一個元素而言prev指向的是list的尾結點,那麼在刪除一個元素的時候還需要判斷該節點是不是first節點進行處理.而在hlist提供的刪除節點的API中,並沒有帶上hlist_head這個參數,因此做這個判斷存在難度.
3)以上兩點說明了為什麼不使用prev,現在來說明為什麼需要的是pprev,也就是一個指向指標的指標來儲存前一個節點的next指標--因為這樣做即使在刪除的節點是first節點時也可以通過*pprev =next;直接修改指標的指向.來看刪除一個節點和修改list頭結點的兩個API:
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是hash的first指標,因此它的pprev指向的是hash的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指標,而當該節點是first節點時指向自己,因此兩種情況下不論該節點是一般的節點還是頭結點都可以通過這個操作刪除掉所需刪除的節點
if (next)
next->pprev = pprev;
}