linux通用hash鏈表

   與我昨天寫的《linux通用鏈表》類似,主要分析linux提供的頭文件代碼(linux/list.h)。


一、結構定義及初始化

/* hash頭節點定義 */
struct hlist_head {
    struct hlist_node *first;
};
/* hash節點定義 */
struct hlist_node {
    struct hlist_node *next, **pprev;
};
/* 頭節點初始化 */
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
/* hash節點初始化 */
#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)

   這裏需要說明兩個問題:

1. 頭節點爲什麼只有一個指針成員,難道不能和hash節點同一定義?

2. hash節點中爲什麼用**pprev,而不和通用鏈表那樣使用*prev?

   hash鏈表的目的就是解決在大量數據下的查找問題。也就是說一般情況下hash桶會很大,頭節點也就會很多。爲了節省內存,設計人員就只使用一個指針,指向hash鏈的第一個節點。這樣就導致頭節點和hash鏈節點的數據結構不一致,有爲了實現通用性就出現了二級指針**pprev,指向前一個節點的*next域。如何看出通用性的,請看下面是如何刪除節點的。


二、刪除節點

   這裏與通用鏈表的刪除節點接口類似,LIST_POISON1和LIST_POISON2可以參考《linux通用鏈表》中介紹。

/* 從hash鏈中刪除節點,不需要考慮是否爲鏈中首節點。
 * 如果將**pprev設計爲*prev,由於頭節點後hash鏈中節點數據結構不一致,
 * 那就要特殊處理鏈中首節點的prev,因爲它指向的數據結構與其他節點
 * prev指向的不一樣。
 */
static inline void __hlist_del(struct hlist_node *n)
{
    struct hlist_node *next = n->next;
    struct hlist_node **pprev = n->pprev;
    *pprev = next;
    if (next)
        next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
    __hlist_del(n);
    n->next = LIST_POISON1;
    n->pprev = LIST_POISON2;
}
static inline void hlist_del_init(struct hlist_node *n)
{
    if (n->pprev)  {
        __hlist_del(n);    //從hash鏈中刪除節點
        INIT_HLIST_NODE(n);//節點初始化
    }
}


三、添加節點

/* 添加到hash鏈的頭部 */
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
    struct hlist_node *first = h->first;
        /* 將原來的first節點連接到n後面 */
    n->next = first;
    if (first)
        first->pprev = &n->next;
        /* 再將n連接到頭結點h */
    h->first = n;
    n->pprev = &h->first;
}
/* 將n添加到next節點前面,但要保證next節點非空 */
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;
}
/* 將next添加到n節點後面 */
static inline void hlist_add_after(struct hlist_node *n,
                    struct hlist_node *next)
{
    next->next = n->next;
    n->next = next;
    next->pprev = &n->next;
        /* next不是添加到hash鏈的尾部,則需要設置下一節點的pprev */
    if(next->next)
        next->next->pprev  = &next->next;
}


四、hash鏈表遍歷

   與標準鏈表中遍歷接口類似,有entry/safe之分。

   先介紹一條語句{ n = pos->next; 1; }。它是複合語句,返回值爲最後的一條語句1;。即該語句永遠爲真。在hlist_for_each函數中,如果pos爲空則循環結束,如果pos不爲空,那麼pos->next可能爲空,但該循環還不能結束,所以就使用{ n = pos->next; 1; }確保&&後面爲真。

/* 返回hash鏈中的用戶數據結構地址 */
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
/* 與標準鏈表類似,該接口內不能添加/刪除節點 */
#define hlist_for_each(pos, head) \
    for (pos = (head)->first; pos; \
         pos = pos->next)
/* 該循環內可以添加刪除節點 */
#define hlist_for_each_safe(pos, n, head) \
    for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
         pos = n)
/* 在循環內,可訪問用戶定義的數據結構(帶有entry關鍵字) */
#define hlist_for_each_entry(tpos, pos, head, member)            \
    for (pos = (head)->first;                     \
         pos &&          \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/* 從pos的下一個節點開始遍歷 */
#define hlist_for_each_entry_continue(tpos, pos, member)         \
    for (pos = (pos)->next;                       \
         pos &&          \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/* 從pos節點開始遍歷 */
#define hlist_for_each_entry_from(tpos, pos, member)             \
    for (; pos&&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/* 安全模式下遍歷,n爲循環時使用的臨時變量,只能對pos操作 */
#define hlist_for_each_entry_safe(tpos, pos, n, head, member)        \
    for (pos = (head)->first;                     \
         pos && ({ n = pos->next; 1; }) &&                \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = n)


五、其他

/* 判斷節點h是否在hash鏈表中。
   很巧妙使用了pprev,該指針指向前一節點的next域 */
static inline int hlist_unhashed(const struct hlist_node *h)
{
    return !h->pprev;
}
/* 判斷hash鏈表是否爲空 */
static inline int hlist_empty(const struct hlist_head *h)
{
    return !h->first;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章