与我昨天写的《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;
}
转载于:https://blog.51cto.com/cizyzhang/1381084