双向链表dlist和哈希链表hlist

list和hlist

题记

之前学习Linux内核的时候对于这两个结构的已经接触过,但是和我最初学习数据结构的构造方式不太一样,很长时间也没能理解,甚至觉得自己直接手写结构也问题不大。直到最近移植了两个功能,深深感觉到linux自带数据结构的库有多么好用,自己造轮子实在很麻烦,也很不利于二次读代码,所以现在把关于hlist和list的内容整理一下,以后再也不会自己造轮子了,用现成的库既好维护也好阅读。

list篇

节点定义

linux库对双链表节点的设计理念是将链表节点塞在其他数据结构中使用,降低数据和结构的耦合性,将对链表的操作全部抽象出来封装成库。

struct list_head {
	struct list_head *next, *prev;
};

初始化

可以很明显看出来有头节点。

这里的双链表是有头节点的结构,即使链表被判断为空,实际上链表中有一个头节点。
双链表是一定要初始化的,根据初始化函数的内容很容易看出,双链表初始化的节点next和prev域指针都是指向自己本节点而不是NULL,所以在之后的调试过程中出现空指针,一般两种情况,一种是代码中写错了,强行访问空指针,另外一种就是没有初始化头节点。

静态初始化

静态初始化只用于定义声明的地方,因为这里都是宏定义的语句,这种结构体声明的写法是只能在声明定义的时候使用,不可以用在之后的赋值中。如果只是需要一个双链表就直接用这个“LIST_HEAD”去声明就好了。

#define LIST_HEAD_INIT(name) { &(name), &(name) }//这种结构体声明的写法是只能在声明的时候使用。

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)
示例:
LIST_HEAD (&my_list_head);

动态初始化

动态初始化是在在声明之后的赋值中使用,一般这种情况是,双链表被挂在其他结构中使用,比如首先大结构是个二叉树,每个二叉树节点都挂一个双链表,这时你的双链表就只能使用这种动态初始化去赋值。

static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}
示例:
struct list_head my_list;
INIT_LIST_HEAD(&my_list);

宏函数操作

编号 函数含义 函数定义 Note
1 根据节点指针取当前节点所在结构体,ptr为当前头节点,type为当前头节点所在的结构体类型名称,member指的是当前头节点所在的结构体中的成员名称。 list_entry(ptr, type, member)
2 遍历双链表,pos为当前头节点,head为散列表的头节点 list_for_each(pos, head) 遍历过程中不能做删除当前节点的操作,一般要配合list_entry取整个结构体数据使用。
3 遍历双链表,n表示pos的下一个头节点 list_for_each_safe(pos, n, head) 遍历过程中可以做删除当前节点的操作,一般要配合list_entry取整个结构体数据使用。
4 遍历双链表,pos指针当前指向的双链表节点,head为双链表的头节点,member指的是当前头节点所在的结构体中的成员名称。 list_for_each_entry(pos, head, member) 遍历过程中不能做删除当前节点的操作
5 上一个遍历宏函数的安全版本 list_for_each_entry_safe(pos, n, head, member)
6 从pos指的这个节点的下一个节点开始遍历整个list list_for_each_entry_continue(pos, head, member)
7 从pos指的这个节点开始遍历整个list list_for_each_entry_from(tpos, pos, member)

函数操作

编号 函数含义 函数定义 Note
1 在head节点后面添加一个new节点 void list_add(struct list_head *new, struct list_head *head)
2 在head节点前面添加一个new节点 void list_add_tail(struct list_head *new, struct list_head *head) 如果head是头节点,这个函数操作效果就是添加一个尾节点
3 在双链表中把entry节点删除 void list_del(struct list_head *entry) 被删除之后的entry节点没有将成员指针置空
4 在双链表中把entry节点删除,并且将entry初始化 void list_del_init(struct list_head *entry) 这里的entry节点是已经被初始化过了。
5 在双链表中用new节点替代old节点 void list_replace(struct list_head *old,
6 在双链表中用new节点替代old节点,且将old节点重新初始化 void list_replace_init(struct list_head *old,
7 将list节点移到head节点后面 void list_move(struct list_head *list, struct list_head *head) 这里的list和head可以不用在一个链表里面
8 将list节点移到head节点前面 void list_move_tail(struct list_head *list, struct list_head *head) 这里的list和head可以不用在一个链表里面
9 list节点是head节点所在双链表的最后一个节点吗,是返回1,不是返回0. int list_is_last(const struct list_head *list, const struct list_head *head) 这里的head节点是双链表的头节点
10 判断head双链表是否为空 int list_empty(const struct list_head *head) 这里看起来头节点是不会被塞进数据结构的,判断只有一个头节点
11 判断head双链表是否为空 int list_empty_careful(const struct list_head *head) 多cpu的安全方法
12 判断head双链表是否只有一个entry int list_is_singular(const struct list_head *head) 除了头节点之外还有一个节点
13 将list从entry的位置切开,从entry后面一个节点开始挂在head后面 void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) list[m, entry, n],经过这个函数之后分为两个链表list[m,entry]和head[entry + 1, n]。
14 将list开头的链表加入到head开头的链表 void list_splice(const struct list_head *list, struct list_head *head) 其中list节点会变成游离节点,要记得释放
15 将list开头的链表加入到head所在链表的尾部 void list_splice_tail(struct list_head *list, struct list_head *head) 其中list节点会变成游离节点,要记得释放
16 将list开头的链表加入到head开头的链表,list节点会被重新初始化,其中的两个指针域被初始化 void list_splice_init(struct list_head *list, struct list_head *head) 其中list节点会变成游离节点,要记得释放
17 将list开头的链表加入到head所在链表的尾部,list节点会被重新初始化,其中的两个指针域被初始化 void list_splice_tail_init(struct list_head *list, struct list_head *head) 其中list节点会变成游离节点,要记得释放

hlist篇

节点定义

struct hlist_head {
	struct hlist_node *first;
};

struct hlist_node {
	struct hlist_node *next, **pprev;//这里的pprev存储前继节点地址的地址。
};

初始化

初始化方法有四个,分别针对hlist_head和hlist_node

[法一]
#define HLIST_HEAD_INIT { .first = NULL }   //不推荐使用

[法二]
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }  //hlist_head静态初始化方法初始化方法,只能在声明的时候使用。
示例:
HLIST_HEAD(my_hlist_head);//声明

[法三]
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)  //hlist_head动态初始化方法初始化方法,必须先声明。
示例:
hlist_head my_hlist_head;//声明
INIT_HLIST_HEAD(&my_hlist_head);//初始化

[法四]
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
	h->next = NULL;
	h->pprev = NULL;
}//hlist_node动态初始化方法初始化方法,必须先声明。
示例:
hlist_node my_hlist_node;//声明
INIT_HLIST_NODE(&my_hlist_node);//初始化

宏函数操作

编号 函数含义 函数定义 Note
1 根据节点指针取当前节点所在结构体,ptr为当前头节点,type为当前头节点所在的结构体类型名称,member指的是当前头节点所在的结构体中的成员名称。 list_entry(ptr, type, member)
2 遍历双链表,pos为当前头节点,head为散列表的头节点 list_for_each(pos, head) 遍历过程中不能做删除当前节点的操作,一般要配合list_entry取整个结构体数据使用。
3 遍历双链表,n表示pos的下一个头节点 list_for_each_safe(pos, n, head) 遍历过程中可以做删除当前节点的操作,一般要配合list_entry取整个结构体数据使用。
4 遍历双链表,pos指针当前指向的双链表节点,head为双链表的头节点,member指的是当前头节点所在的结构体中的成员名称。 list_for_each_entry(pos, head, member) 遍历过程中不能做删除当前节点的操作
5 上一个遍历宏函数的安全版本 list_for_each_entry_safe(pos, n, head, member)
6 从pos指的这个节点的下一个节点开始遍历整个list list_for_each_entry_continue(pos, head, member)
7 从pos指的这个节点开始遍历整个list list_for_each_entry_from(tpos, pos, member)

函数操作

编号 函数含义 函数定义 Note
1 判断该节点是否已经存在hash表中 static inline int hlist_unhashed(const struct hlist_node *h) 通过判断hlist_node前继节点是否为NULL,一个经过初始化的hlist_node才能准确判断,所以节点在使用之前一定要初始化。如果返回为0,则该节点存在hash表中;如果返回不为0,则不存在与hash表中。
2 判断hash表中该item是否为空 static inline int hlist_empty(const struct hlist_head *h) 通过判断hlist_node后继节点是否为NULL。如果返回为0,则该节点不存在在hash表中;如果返回不为0,则存在与hash表中。
3 删除一个hlist_node节点 static inline void hlist_del(struct hlist_node *n) 被删除之后的节点在函数中会被初始化。
4 删除一个hlist_node节点 static inline void hlist_del_init(struct hlist_node *n) 在上函数基础上会先判断这个节点是否已经存在hash表中,存在表中才会删除。
5 在hash的某个item的头部h添加一个n节点 static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
6 在hash表中next节点的前面添加n节点 static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) 先修改n的前继和后继指针,然后修改前继指针的内容。
7 在hash表中n节点的后面添加next节点 static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next)
posted @ 2022-01-22 20:14  哎呀是不是疯啦  阅读(624)  评论(0编辑  收藏  举报