双向链表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为散列表的头节点 | hlist_for_each(pos, head) | 遍历过程中不能做删除当前节点的操作,一般要配合hlist_entry取整个结构体数据使用。 |
3 | 遍历散列表中的每一个散列项,n表示pos的下一个头节点 | hlist_for_each_safe(pos, n, head) | 遍历过程中可以做删除当前节点的操作,一般要配合hlist_entry取整个结构体数据使用。 |
4 | 遍历散列表中的每一个散列项,pos为当前头节点,tpos为当前头节点所在的结构体指针,head为散列表的头节点,member指的是当前头节点所在的结构体中的成员名称。 | hlist_for_each_entry(tpos, pos, head, member) | 遍历过程中不能做删除当前节点的操作 |
5 | 上一个遍历宏函数的安全版本 | hlist_for_each_entry_safe(tpos, pos, n, head, member) | |
6 | 从pos指的这个节点的下一个节点开始遍历整个hlist | hlist_for_each_entry_continue(tpos, pos, member) | |
7 | 从pos指的这个节点开始遍历整个hlist | hlist_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指的是当前头节点所在的结构体中的成员名称。 | hlist_entry(ptr, type, member) | |
2 | 遍历散列表中的每一个散列项,pos为当前头节点,head为散列表的头节点 | hlist_for_each(pos, head) | 遍历过程中不能做删除当前节点的操作,一般要配合hlist_entry取整个结构体数据使用。 |
3 | 遍历散列表中的每一个散列项,n表示pos的下一个头节点 | hlist_for_each_safe(pos, n, head) | 遍历过程中可以做删除当前节点的操作,一般要配合hlist_entry取整个结构体数据使用。 |
4 | 遍历散列表中的每一个散列项,pos为当前头节点,tpos为当前头节点所在的结构体指针,head为散列表的头节点,member指的是当前头节点所在的结构体中的成员名称。 | hlist_for_each_entry(tpos, pos, head, member) | 遍历过程中不能做删除当前节点的操作 |
5 | 上一个遍历宏函数的安全版本 | hlist_for_each_entry_safe(tpos, pos, n, head, member) | |
6 | 从pos指的这个节点的下一个节点开始遍历整个hlist | hlist_for_each_entry_continue(tpos, pos, member) | |
7 | 从pos指的这个节点开始遍历整个hlist | hlist_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) |