内核经典数据结构list 剖析
前言:
linux内核中有很多经典的数据结构,list(也称list_head)为其中之一,这些数据结构都是使用C语言实,并且定义和实现都在单独的头文件list.h中。可以随时拿出来使用。
list.h的定义不同linux发行版本路径不同,我们可以在/usr/include目录下find。
虽然list_head是基于C语言实现,但其设计初衷是使用在linux内核中,所以其中用了许多GUN的方法而并非C标准,如typeof,该宏的作用是获取变量类型。所以list_head
存在一定的平台相关性,如果要做跨平台需要进行改造。现我们只对内核原始list_head进行详细说明及实例演示。
原理:
list_head属于一个双向循环链表,表头只是一个指针。当链表为空时,表头的前驱和后继分别指向自己。
list_head的特点:
不包含数据,链表中只有prev和next,这样使得我们的链表使用起来就比较灵活,使用时只需要将其包含进使用的对象即可,有一种面向对象的感觉。例如:在使用时只需要在宿主结构中嵌入list_head类型节点,并将节点插入到list_head表头中。
这样多个宿主结构体内容就通过链表关联了起来。使用时只需要使用宿主结构中的list_head节点反推宿主结构体的首地址就可以访问宿主结构体。
相对于传统链表的包含数据,解决了链表结构冗余,通用性差,不方便移植等问题。
该链表不包含互斥锁,所以在多线程使用下需要加互斥锁进行同步,如下:
struct list_item
{
list_head list;
pthread_mutex_t lmutex;
}
struct list_item g_list_item;//包含了链表头及互斥锁。
链表定义:
struct list_head {
struct list_head *next, *prev;
};
实现方法:
函数说明:
作用 | 接口类型 | 函数原型 | 参数说明 | 函数说明 | 备注 |
增加 | 内部接口 | static inline void __list_add(struct list_head *xnew, struct list_head *prev,struct list_head *next) { next->prev = xnew; xnew->next = next; xnew->prev = prev; prev->next = xnew; } | xnew:新增节点 prev:插入位置的前一个节点 next:插入位置的后一个节点 | 双向循环链表的插入。 对于双向循环链表,节点插入时我们一般需要考虑4个元素:插入节点前驱;插入节点后继;插入位置前的一个节点后继;插入位置后的一个点解前驱; 所以这里的prev我们可以理解为插入位置前的一个节点,next则为插入位置后的一个节点; | |
增加 | 对外接口 | static inline void list_add(struct list_head *xnew, struct list_head *head) { __list_add(xnew, head, head->next); } | xnew:新增节点 head:链表头 | 头插法的方式插入链表。 | 尾插与头插不同,从这里调用__list_add函数时传参进行区分。 |
增加 | 对外接口 | static inline void list_add_tail(struct list_head *xnew, struct list_head *head) { __list_add(xnew, head->prev, head); } | xnew:新增节点 head:链表头 | 尾插法的方式插入链表。 | |
删除 | 内部接口 | static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } | prev:要删除节点的前驱。 next:要删除节点的后继。 | 将要删除节点的前驱作为下一个节点的前驱;将后继作为前一个节点的后继;这样要删除的节点就与原链表断开。 同时该节点删除后,该节点原先的前驱和后继连了起来,保证了链表不产生断点。 | |
删除 | 对外接口 | static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } | entry:要删除的节点 | 删除节点,并将节点的next,prev分别指向LIST_POISON1,LIST_POISON2。 | 两个函数都调用了__list_del函数,但是list_del最后将prev,next分别指向了LIST_POSTION2和LIST_POSTION1两个特殊位置,对这两个位置的访问会引起页故障。属于不安全函数;而list_del_init最后调用INIT_LIST_HEAD将next,prev都指向了自己,属于安全函数。一般建议使用list_del_init。 注意:如果链表中的节点是通过malloc或new申请的看空间,这里调用删除接口后需要取手动释放空间 |
删除 | 对外接口 | static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } | entry:要删除的节点 | 删除节点,并将节点的next,prev指向自己,即初始化。 | |
替换 | 对外接口 | static inline void list_replace(struct list_head *old, struct list_head *xnew) { xnew->next = old->next; xnew->next->prev = xnew; xnew->prev = old->prev; xnew->prev->next = xnew; } | old:被替换的节点 new:替换节点 | 使用新的节点替换原有的旧节点。 | 通过实现可以看出list_replace函数替换后old节点与new节点指向的前驱和后继都一样,属于不安全函数;而list_replace_init则会将old的前驱和后继分开,属于安全函数;一般建议使用list_replace_init。 |
替换 | 对外接口 | static inline void list_replace_init(struct list_head *old, struct list_head *xnew) { list_replace(old, xnew); INIT_LIST_HEAD(old); } | old:被替换的节点 new:替换节点 | 使用新的节点替换原有的旧节点。 | |
移动 | 对外接口 | static inline void list_move(struct list_head *list, struct list_head *head) { __list_del(list->prev, list->next); list_add(list, head); } | list:要移动的节点 head:表头 | 移动节点到head之后 | 移动接口如果的操作的是非链表内部元素,则相当于增加接口的作用。 |
移动 | 对外接口 | static inline void list_move_tail(struct list_head *list,struct list_head *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } | list:要移动的节点 head:表头 | 移动节点到head之前 | |
判空 | 对外接口 | static inline int list_empty(const struct list_head *head) { return head->next == head; } | head:表头 | 判断链表是否为空,返回1表示链表为空,0表示非空。 | |
判空 | 对外接口 | static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); } | head:表头 | 判断链表是否为空,返回1表示链表为空,0表示非空。 | 因为内核在运行时候可能有多个进程同时修改内存,判断链表是否为空需要判断他的前一个节点和后一个节点是否为空。但是注释也承认了这种判断方法的不安全性,要是保证安全操作,还需要加锁保护。 |
判断是否为最后一个节点 | 对外接口 | static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; } | list:当前节点 head:链表表头 | 判断当前节点是否为链表的最后一个节点。 | |
链表合并 | 内部接口 | static inline void __list_splice(const struct list_head *list, struct list_head *prev,struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } | list:被合并的链表 prev:合并位置的前一个节点 next:合并位置的后一个节点 | ist作为被合并链表的表头,合并相当于将一个链表作为一个整体插入到另一个链表的某一个位置。 first作为表头list的后继,last作为表头list的前驱;表头list的后继的前驱(first→prev)指向合并位置的前一个节点;表头list的前驱的后继(last→next)指向合并位置的后一个节点。 注意:合并后的方要保持一致,还是一个环形。 | |
链表合并 | 对外接口 | static inline void list_splice(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } | list:被合并的链表 head:将list合并到head链表 | 将list链表合并到head链表,合并方式为头插法合入,即将list加入到head之后。 该函数内部实现调用了__list_splice,合并原理见__list_splice详细说明 | list_splice于list_splice_init的区别是list_splice_init在合并后将list进行了初始化,属于安全函数。 建议使用list_splice_init |
链表合并 | 对外接口 | static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } | |||
链表合并 | 对外接口 | static inline void list_splice_tail(struct list_head *list,struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } | list:被合并的链表 head:将list合并到head链表 | 将list链表合并到head链表,合并方式为尾插法合入,即将list加入到head之前。 | list_splice_tail于list_splice_tail_init的区别是list_splice_tail_init在合并后将list进行了初始化,属于安全函数。 建议使用list_splice_tail_init |
链表合并 | 对外接口 | static inline void list_splice_tail_init(struct list_head *list,struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head->prev, head); INIT_LIST_HEAD(list); } } |
部分宏说明:(附demo)
宏定义 | 实现 | 说明 | 备注 |
offsetof | #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) | 获取变量在宿主结构中的偏移量 | (TYPE*)0 这个是欺骗编译器,有一个TYPE类型的指针,它的地址是0;然后对这个指针的MEMBER取地址,因为基地址是0,所以对MEMBER取地址后就是MEMBER在TYPE中的偏移量了。
欺骗编译器的用法类似如下: int *p = (int *)0; 打印p的地址是nil |
container_of | #define container_of(ptr, type, member) (\ {const typeof( ((type *)0)->member ) *__mptr = (ptr);\ (type *)( (char *)__mptr - offsetof(type,member) );}) | 根据结构中某个成员的地址获取宿主结构的地址 ptr:成员地址。 type:宿主结构类型。 member:宿主结构中定义的成员。与ptr同类型。 使用与list_entry相同。 | 由于type是类型,member是成员,所以这里还是使用(type *)0)->member则表示type类型指针的member成员,然后用typeof取member的类型,这样__mptr就与member成员类型是一样的,最终目的还是为了定义一个与ptr类型相同的中间变量,并且将ptr赋值给它。 (type *)( (char *)__mptr - offsetof(type,member) ); 这一步骤是通过成员现地址减去成员偏移量计算宿主结构体首地址,这里必须对mptr强转成(char*)类型指针,原因是指针加减操作不同于我们int变量的加减, 比如指针的+1操作是指针当前地址+1*N(指针类型大小)。强转成char*后,+n就是指针当前地址+n。如果不强转,直接偏移会越界。 其实定于中间变量这一步我们可以省略,因为我们是通过成员prt的地址来计算宿主结构体的首地址,所以我们可以直接使用ptr,现对container_of改造如下: #define container_of(ptr, type, member) (\ {(type*)((char *)ptr - offsetof(type,member));}) |
list_entry | #define list_entry(ptr, type, member) \ container_of(ptr, type, member) | 根据成员ptr的地址获取宿主结构体的首地址。 type:宿主结构体的类型。 member:ptr在宿主结构体中的成员变量名。 | struct student { char name[32]; int age; struct list_head list; }; ptr则表示一个一个list_head类型的指针,type则是struct student类型,member则是成员变量list。 |
list_first_entry | #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) | 根据成员ptr的地址获取ptr下一个节点的宿主结构体首地址。 type:宿主结构体的类型 member:ptr在宿主结构体中的成员变量名。 | 同list_entry |
list_for_each | #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) | 从head之后的一个节点开始向后循环,一般与list_entry或其它方法配合使用。循环时节点无法操作。 pos:list_head类型的指针。 head:链表头。 | void Data_list_for_each() { struct list_head *postion = NULL; struct student *p = NULL; /* 参数1:struct list_head结构体指针 参数2:头指针 */ //list_for_each_prev(postion,&g_list) list_for_each(postion,&g_list) { /* 参数1:struct list_head结构体指针 参数2:链表容器结构体类型,这里即struct student 参数3:链表容器中链表的成员变量 */ //二选一,list_entry的实现内部其实调用了container_of p = list_entry(postion,struct student,list); //p = container_of(postion,struct student,list); printf("%s-%d\n",p->name,p->age); } return; } |
list_for_each_prev | #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); \ pos = pos→prev) | 从head之后的一个节点开始向前循环,一般与list_entry或其它方法配合使用。 pos:list_head类型的指针。 head:链表头。 | |
list_for_each_safe | #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) | list_for_each的安全版本,循环时可以操作节点。 从该宏的实现我们可以看到,相对于list_for_each多了一个临时变量n | void Data_list_del_1() { struct list_head *postion = NULL; struct list_head *tmp = NULL; struct student *p = NULL; list_for_each_safe(postion,tmp,&g_list) { p = list_entry(postion,struct student,list); list_del(&p->list); //list_del_init(&p->list); free(p); } return; } |
list_for_each_entry | #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) | 与list_for_each功能相似,只是参数不同,这里的pos是宿主结构体类型,我们可以理解为这个就是list_for_each与list_entry的使用结合体。 比如我们要使用list_for_each对链表进行遍历,并且遍历宿主结构体时就需要与list_entry进行配合。list_for_each_entry刚好实现了这个功能。 pos:宿主结构体类型指针。 head:链表头。 member:宿主结构中链表的成员变量。 | void Data_list_for_each_entry() { struct student *p = NULL; /* 参数1:链表容器结构体指针 参数2:头指针 参数3:链表容器中链表的成员变量 */ list_for_each_entry(p,&g_list,list) { printf("%s-%d\n",p->name,p->age); } return; } |
list_for_each_entry_reverse | #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) | 反向遍历,list_for_each_entry为正向遍历 | void Data_list_for_each_entry_reverse() { struct student head; struct student *p = NULL; //参数定义同Data_list_for_each_entry函数 list_for_each_entry_reverse(p,&g_list,list) { printf("%s-%d\n",p->name,p->age); } return; } |
list_for_each_entry_continue | #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) | 从某个节点开始遍历。 需要与list_prepare_entry(pos,head,member) 配合使用,list_prepare_entry的返回值作为list_for_each_entry_continue的pos。 list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的函数多了一个中间的临时变量。 pos:宿主结构体类型指针。 head:链表头。 member:宿主结构中链表的成员变量。 | void Data_list_for_each_entry_continue() { struct student *pos = NULL; struct student *n = NULL; n = list_prepare_entry(pos,&g_list,list); /* list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过 list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的 函数多了一个中间的临时变量。 */ list_for_each_entry_continue(n,&g_list,list) { printf("%s-%d\n",n->name,n->age); } return; } |
list_for_each_entry_safe | #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) | list_for_each_entry的安全版本,即在遍历的时候可以操作节点。属于正向遍历。 这里我们看到用临时节点n保存了pos的下一个节点地址,这样我们就可以进项删除操作了,删除pos之后,再将n赋值给pos, 整个链表结构是完整的。 pos:宿主结构体类型指针。 n:宿主结构体临时指针。 head:链表头。 member:宿主结构中链表的成员变量。 | void Data_list_del_2() { //删除链表中元素 struct student *n = NULL; struct student *pos = NULL; list_for_each_entry_safe(pos,n,&g_list,list) { /*list_del与list_del_init都是删除节点,但是list_del_init属于安全删除*/ //list_del(&pos->list); list_del_init(&pos->list); free(pos); } return; } |
list_for_each_entry_safe_continue | #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) | 同list_for_each_entry_continue,属于安全函数,遍历的时候节点看可以进行操作,例如删除。 | |
list_for_each_entry_safe_reverse | #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) | 功能与list_for_each_entry_safe类似,属于反向遍历。 | |
平台通用性及移植
通用性
list_head虽然时使用C语言实现的,但是其设置的初衷是使用在linux内核,所以引入了GUN的一些方法,如typeof,该宏是用于获取参数的类型。typeof在windows,freeRtos等平台是不支持,所以在不同平台使用需要做平台适配。
例如在win32下我们可以如下定义:
#define list_for_each_entry(pos, head, member, type) \
for (pos = list_entry((head)->next, type, member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, type, member))
跨平台移植
跨平台移植时list_head的实现只保留C标注,其余GUN的方法我们需要完全使用C标准的方式进行替换。
示例代码
#include "list.h"
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>struct list_head g_list;/*头指针*/struct student
{char name[32];int age;struct list_head list;
};int Data_Add(char *name,int age,int len)
{if(name == NULL){printf("NULL......\n");return -1;}struct student *st = malloc(sizeof(struct student));if(st == NULL){printf("malloc failed,reason:%d-%s\n",errno,strerror(errno));return -1;}memcpy(st->name,name,len);st->age = age;INIT_LIST_HEAD(&(st->list));//二选一//头插法//list_add(&(st->list),&g_list);//尾插法list_add_tail(&(st->list),&g_list);return 0;
}//方法1遍历
void Data_list_for_each()
{struct list_head *postion = NULL;struct student *p = NULL;/*参数1:struct list_head结构体指针参数2:头指针*/list_for_each(postion,&g_list){/*参数1:struct list_head结构体指针参数2:链表容器结构体类型,这里即struct student参数3:链表容器中链表的成员变量*///二选一,list_entry的实现内部其实调用了container_ofp = list_entry(postion,struct student,list);//p = container_of(postion,struct student,list);printf("%s-%d\n",p->name,p->age);}return;
}//方法2遍历
void Data_list_for_each_entry()
{struct student *p = NULL;/*参数1:链表容器结构体指针参数2:头指针参数3:链表容器中链表的成员变量*/list_for_each_entry(p,&g_list,list){printf("%s-%d\n",p->name,p->age);}return;
}//从某个节点开始遍历
/*需要与list_prepare_entry(pos,head,member) 配合使用,list_prepare_entry的返回值作为list_for_each_entry_continue的pos
*/
void Data_list_for_each_entry_continue()
{struct student *pos = NULL;struct student *n = NULL;n = list_prepare_entry(pos,&g_list,list);/*list_for_each_entry_safe_continue与list_for_each_entry_continue一样,只不过list_for_each_entry_safe_continue遍历后可以直接操作节点,比如删除。因此带safe的函数多了一个中间的临时变量。*/list_for_each_entry_continue(n,&g_list,list){printf("%s-%d\n",n->name,n->age);}return;
}//方法1反向遍历
void Data_list_for_each_prev()
{struct list_head *postion = NULL;struct student *p = NULL;//参数定义同Data_list_for_each函数list_for_each_prev(postion,&g_list){p = list_entry(postion,struct student,list);printf("%s-%d\n",p->name,p->age);}return;
}//方法2反向遍历
void Data_list_for_each_entry_reverse()
{struct student head;struct student *p = NULL;//参数定义同Data_list_for_each_entry函数list_for_each_entry_reverse(p,&g_list,list){printf("%s-%d\n",p->name,p->age);}return;
}//数据替换1
void Data_list_replace()
{//用liangchaowei替换zhouxingxingstruct student head;struct student *pos = NULL;struct student *data = malloc(sizeof(struct student));if(data == NULL){return ;}memcpy(data->name,"liangchaowei",sizeof("liangchaowei"));data->age = 57;list_for_each_entry(pos,&g_list,list){if(memcmp(pos->name,"zhouxingxing",sizeof("zhouxingxing")) == 0){/*list_replace没有将pos的前驱和后继分开,所以建议使用list_replace_init*///list_replace(&pos->list,&data->list);list_replace_init(&pos->list,&data->list);free(pos);break;}}return;
}//方法1删除
//使用与Data_list_for_each类似的方法进行遍历删除
void Data_list_del_1()
{struct list_head *postion = NULL;struct list_head *tmp = NULL;struct student *p = NULL;list_for_each_safe(postion,tmp,&g_list){p = list_entry(postion,struct student,list);if(memcmp(p->name,"liangchaowei",sizeof("liangchaowei")) == 0){list_del(&p->list);//list_del_init(&p->list);free(p);}}return;
}//方法2删除
//删除节点名为liangchaowei的节点
void Data_list_del_2()
{//删除链表中元素struct student *n = NULL;struct student *pos = NULL;list_for_each_entry_safe(pos,n,&g_list,list){if(memcmp(pos->name,"zhangguorong",sizeof("zhangguorong")) == 0){/*list_del与list_del_init都是删除节点,但是list_del_init属于安全删除*///list_del(&pos->list);list_del_init(&pos->list);free(pos);}}return;
}/*移动节点,将某个第三方节点添加到当前链表上,功能与新增节点相似*/
int Data_list_move(char *name,int age,int len)
{if(name == NULL){printf("NULL......\n");return -1;}struct student *st = malloc(sizeof(struct student));if(st == NULL){printf("malloc failed,reason:%d-%s\n",errno,strerror(errno));return -1;}memcpy(st->name,name,len);st->age = age;INIT_LIST_HEAD(&(st->list));/*相当于将节点进行头部插入到链表*///list_move(&(st->list),&g_list);/*相当于将节点进行尾部插入到链表*/list_move_tail(&(st->list),&g_list);return 0;
}int main()
{int res = -1;INIT_LIST_HEAD(&g_list);//数据添加res = Data_Add("liudehua",50,sizeof("liudehua"));res &= Data_Add("zhangxueyou",51,sizeof("zhangxueyou"));res &= Data_Add("zhourunfa",52,sizeof("zhourunfa"));res &= Data_Add("zhangxueyou",55,sizeof("zhangxueyou"));res &= Data_Add("zhouxingxing",56,sizeof("zhouxingxing"));res &= Data_Add("zhangguorong",60,sizeof("zhangguorong"));if(res != 0){return -1;}//方法1遍历printf("------------list for each-------------------\n\n");Data_list_for_each();//方法2遍历printf("------------list for each entry-------------------\n\n");Data_list_for_each_entry();//方法1反向遍历printf("------------------list for each prev-------------\n\n");Data_list_for_each_prev();//方法2反向遍历printf("---------------list for each entry reverse----------------\n\n");Data_list_for_each_entry_reverse();//从某个节点开始遍历printf("----------------list for each entry continue---------------\n\n");Data_list_for_each_entry_continue();//数据替换printf("-----------------replace--------------\n\n");Data_list_replace();Data_list_for_each_entry();//数据删除printf("-----------------del 1--------------\n\n");Data_list_del_1();Data_list_for_each_entry();//数据删除printf("-----------------del 2--------------\n\n");Data_list_del_2();Data_list_for_each();//节点移动printf("-----------------move--------------\n\n");Data_list_move("linqingxia",61,sizeof("linqingxia"));Data_list_move("gutianle",62,sizeof("gutianle"));Data_list_for_each();return 0;
}
相关文章:
内核经典数据结构list 剖析
前言:linux内核中有很多经典的数据结构,list(也称list_head)为其中之一,这些数据结构都是使用C语言实,并且定义和实现都在单独的头文件list.h中。可以随时拿出来使用。list.h的定义不同linux发行版本路径不同,我们可以在/usr/incl…...
华为OD机试 - 考优选核酸检测点(Python)| 真题+思路+考点+代码+岗位
优选核酸检测点 题目 张三要去外地出差,需要做核酸,需要在指定时间点前做完核酸, 请帮他找到满足条件的核酸检测点。 给出一组核酸检测点的距离和每个核酸检测点当前的人数给出张三要去做核酸的出发时间 出发时间是 10 分钟的倍数 同时给出张三做核酸的最晚结束时间题目中…...
在魔改PLUS-F5280开发板上使用合封qsp iflash
文章目录引言硬件调整软件调整总结引言 由于目前灵动官网暂未发布正式版的PLUS-F5280开发板,可以使用现有的PLUS-F5270 v1.2开发板(下文简称PLUS-F5270开发版)替换为MM32F5280微控制器芯片,改装为PLUS-F5280开发板。本文记录了使…...
uni-app 瀑布流
效果图 一、组件 components/u-myWaterfall.vue <template><view class"u-waterfall"><view id"u-left-column" class"u-column"><slot name"left" :leftList"leftList"></slot></view&…...
华为OD机试 - 去除多余空格(Python)| 真题+思路+考点+代码+岗位
去除多余空格 题目 去除文本多余空格,但不去除配对单引号之间的多余空格。给出关键词的起始和结束下标,去除多余空格后刷新关键词的起始和结束下标。 条件约束: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQABYuJD-1676475739950)(https://…...
MyBatis 二级缓存简单使用步骤
1、二级缓存使用 在 MyBatis 中默认二级缓存是不开启的,如果要使用需手动开启。在 mybatis-config.xml 配置文件中设置 cacheEnabled true ,配置如下: <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE c…...
kubeadmin kube-apiserver Exited 始终起不来查因记录
kubeadmin kube-apiserver Exited 始终起不来查因记录 [rootk8s-master01 log]# crictl ps -a CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD b7af23a98302e …...
论文投稿指南——中文核心期刊推荐(工程材料学)
【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…...
【动态规划】背包问题题型及方法归纳
背包问题的种类 背包问题是在规定背包容量为j的前提下,每个物品对应的体积为v[i],价值为w[i],从物品0到物品i中选择物品放入背包中,找出符合某种要求的价值。 (1)背包问题种类 01背包:每种物…...
全球十大资质正规外汇期货平台排行榜(最新版汇总)
外汇期货简称为FxFut,是“Forex Futures”的缩写,是在集中形式的期货交易所内,交易双方通过公开叫价,以某种非本国货币买进或卖出另一种非本国货币,并签订一个在未来的某一日期根据协议价格交割标准数量外汇的合约。 …...
使用Paramiko时遇到的一些问题
目录 1.背景 2.问题合集 1)“bash: command not found” 2)Paramiko中正常的输入,却到了stderr,而stdout是空 3)命令实际是alias 1.背景 在自动化脚本中,使用了库Paramiko,远程SSH到后台服…...
数据预处理(无量纲化、缺失值、分类特征、连续特征)
文章目录1. 无量纲化1.1 sklearn.preprocessing.MinMaxScaler1.2 sklearn.preprocessing.StandardScaler2. 缺失值3. 分类型特征4. 连续型特征数据挖掘的五大流程包括:获取数据数据预处理特征工程建模上线 其中,数据预处理中常用的方法包括数据标准化和归…...
【C#基础】C# 运算符总结
序号系列文章2【C#基础】C# 基础语法解析3【C#基础】C# 数据类型总结4【C#基础】C# 变量和常量的使用文章目录前言运算符1,算术运算符2,布尔逻辑运算符3,位运算符4,关系运算符5,赋值运算符6,其他运算符7&am…...
存储性能软件加速库(SPDK)
存储性能软件加速库SPDK存储加速存储性能软件加速库(SPDK)SPDK NVMe驱动1.用户态驱动1)UIO2)VFIOIOMMU(I/O Memory Management Unit)3)用户态DMA4)大页(Hugepage…...
微服务(五)—— 服务注册中心Consul
一、引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>二、配置yml文件 server:port: 8006spring:application:name: cloud-payment-con…...
冷冻电镜 - ChimeraX Density Map 密度图 操作
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/129055160 由冷冻电镜所生成的Volume,需要观察其内部结构,使用ChimeraX进行操作。 加载Volumes,例如my_volume.mrc 效果如下: 高斯滤波 在命令行(Co…...
Matlab 点云旋转之轴角式
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 三维空间中表示旋转的方法有很多种,轴角式是其中非常经典的一种表示方式。虽然欧拉角表示旋转的方法很是常用,但欧拉角存在着万向锁这个问题,因此轴角式旋转在旋转使用中更为合适。其原理也很是明了,如下所述:…...
2023美赛数学建模资料思路模型
美赛我们为大家准备了大量的资料,我们会在比赛期间给大家分析美题目和相关的思路 全文都是干货,大家仔细阅读,资料文末自取! 首先我们来看美赛23年题型的一个变化: 美赛23年题目变化: A题:连…...
Nginx配置HTTP强制跳转到HTTPS
https 访问我们的测试域名 https://www.xxx.com 站点,但是当我们直接在浏览器地址栏中直接输入 www.xxx.com 的时候却发现进入的是 http 协议的网站,这与我们的初衷不一致。由于浏览器默认访问域名使用的是80端口,而当我们使用SSL证书后&…...
从实现到原理,聊聊Java中的SPI动态扩展
原创:微信公众号 码农参上,欢迎分享,转载请保留出处。 八股文背多了,相信大家都听说过一个词,SPI扩展。 有的面试官就很喜欢问这个问题,SpringBoot的自动装配是如何实现的? 基本上,…...
3、MySQL字符集
1.MySQL字符集和校验规则 字符集:是一套符号和编码的规则校验规则:是对该套符号和编码的校验,定义字符的排序和比较规则,其中是否区分大小写,跟校验规则有关。2.查看字符集方法 netstat -lntup |grep 3306 tcp6 0 0 :::3306 :::* …...
大漠插件最新中文易语言模块7.2302
模块名称:大漠插件中文模块最新通用7.2302模块简介:大漠插件中文模块最新通用7.2302模块特色:原翻译:花老板完善命令备注:易生易世本人花费一个月时间才将命令完善了插件的备注说明.且用且珍惜去掉了大漠插件定制版类.因为没用.模块特色:什么是中文模块?大漠插件模块是由大漠类…...
极客大挑战 2021
题量很大,收获挺多,持续时间也长,据说结束之后会再持续一段时间,然后题目会开源。 WEB Dark 暗网签到,难以置信 Welcome2021 改个请求方法会提示你文件,再进去就好了 babysql 直接把请求包扔sqlmap里&…...
C#开发的OpenRA加载文件的管理
C#开发的OpenRA加载文件的管理 在前面我们分析了mod.yaml文件,发现里面有很多文件列表, 比如下像下面的文件: Packages: ~^SupportDir|Content/cnc ~^SupportDir|Content/cnc/movies ^EngineDir $cnc: cnc ^EngineDir|mods/common: common ~speech.mix ~conquer.mix ~sounds…...
SSM实现文件上传
目录 SSM实现文件上传 1、修改from表单请求方式改为post,添加属性 2、修改springmvc配置文件,添加一下配置 3、后端方法 SSM实现文件上传 1、修改from表单请求方式改为post,添加属性: enctype"multipart/form-data"…...
OPENCV计算机视觉开发实践-图像的基本概念
1.图像与图形: 图像->客观世界的反映,图与像之结合 图->物体透射光与反射光的分布 像->人的视觉得对图的认识 图像->通过照相,摄像,扫描产生. 图形->通过数学规则产生,或者具有一定规则的图案.用一组符号或线条表示性质. 2.数字图像: 数字图像->称数码图像或…...
Android 9.0 ResolverActivity.java多个app选择界面去掉始终保留仅有一次
1.前言 在9.0的系统rom定制化开发过程中,在系统中安装同类型多个app的时候,在系统启动的过程中,会在启动launcher或播放器的过程中,在启动的过程中都是弹出选择框的,然后在选择启动哪个app,这些选择都是在ResolverActivity.java中完成的,所以需要在ResolverActivity.java…...
【算法 | 例题简答】相关例题讲解
目录 简答题 计算题 时间复杂度的计算 递归算法计算 背包问题(0-1背包问题) 回溯法 动态规划法 编程题 用回溯法解方程 动态规划法解决蜘蛛吃蚊子 用分治法解决抛硬币问题 用二分法分两边求最大值 简答题 1、什么是算法?算法有哪…...
浅谈AQS
1.前言 AQS是AbstractQueuedSynchronizer(抽象同步队列)的简写,它是实现同步器的基础组件,并发包下的锁就是通过AQS实现的。作为开发者可能并不会直接用到AQS,但是知道其原理对于架构设计还是很有帮助的。 那为什么说…...
关于服务连接器(Servlet)你了解多少?
Servlet 1 简介 Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义…...
网站建设的测试/西安seo外包公司
首先挂一个论文链接:https://doi.org/10.1093/bioinformatics/btac456 代码链接: https://github. com/Docurdt/DEMoS.git 摘要: 动机:癌症基因组图谱(TCGA)倡议提出的基于综合多组学资料的胃癌(腺癌)分子分型为四种主要亚型,代表了患者分…...
网站商城建设的维度/百度知道免费提问
wget:使用yum安装文件之前,要先确定一下/etc/yum.repos.d下的文件是否改变 在使用yum安装wget inotify:yum -y install inotify-tools scp:可以在有scp命令的电脑上查询一下scp的包名 # which scp # rpm -qf /usr/bin/scp scp的安…...
建设网站思维导图/今日疫情最新数据
我试图用Matplotlib在等高线上绘制一些点。我有标量场,我想从中绘制轮廓。然而,myndarray的维数为0 x 20,但实际空间从-4到4不等。我可以用这段代码绘制这个等高线:x, y numpy.mgrid[-4:4:20*1j, -4:4:20*1j]# Draw the scalar f…...
app开发方式/宁波seo网络推广推荐
希望自己能够通过对本课程的学习,对C语言能有进一步的了解,能够学会自主运用,学习到经验技术和知识,也希望老师能够在学习新知识时多讲解多运用,反复练习,以增加学生对新知识的熟练度和理解度。转载于:http…...
怎样做返利网站/千牛怎么做免费推广引流
前言 在前一篇文章中我们学习了Java虚拟机的结构原理与运行时数据区域,那么我们大概知道了Java虚拟机的内存的概况,那么内存中的数据是如何创建和访问的呢?这篇文章会给你答案。 1.对象的创建 对象的创建通常是通过new一个对象而已࿰…...
12306网站很难做吗/广告网站留电话不用验证码
1 doctype写完整<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 IE请使用标准模式转载于:https://www.cnblogs.com/sunzhihua/p/8425282.html...