当前位置: 首页 > news >正文

libevent源码剖析-基本数据结构

1 简介

    前面系列文章对libevent源码的主体结构,从reactor框架实现,到evbuffer和bufferevent实现原理,及libevent的例子进行了剖析,自此,我们便可基于libevent开发app了。

    从本文开始,主要来介绍下libevent源码的枝叶部分,包括基本数据结构C开发技巧工具函数等。

2 基本数据结构

2.1 单链表

    单链表相关操作都在libevent源码根目录compat/sys/queue.h文件下。这里简要介绍下单链表原理及其操作。

    单链表是一种链式存储的数据结构,每个节点包含数据域和指向下一个节点的指针。下面是单链表的主要优缺点:

优点

  • 动态内存分配:单链表的节点可以在需要时动态分配和释放,不需要提前分配固定大小的内存,适合需要频繁插入和删除的情况。

  • 插入和删除操作效率高:在已知位置进行插入和删除操作只需更改指针即可,时间复杂度为 O(1)。相比数组无需移动其他元素,因此适合频繁插入、删除操作的场景。

  • 节省内存:单链表只需存储数据和一个指向下个节点的指针,比双向链表节省内存开销,适用于内存敏感的应用。

  • 便于扩展:由于链表不需要连续的内存空间,所以扩展链表时只需增加节点,不会涉及整体内存的重新分配和复制操作。

缺点

  • 随机访问性能差:单链表不支持随机访问,要访问某个特定位置的节点,必须从头开始逐个遍历,时间复杂度为 O(n),在访问频繁的场景性能较低。

  • 额外的存储开销:每个节点需要存储一个指针用于链接下一个节点,若数据较小或节点较多,指针的额外存储开销会显得较大。

  • 不便于反向遍历:单链表只能从头到尾顺序遍历,无法反向遍;如果需要反向遍历的功能,需要转换成双向链表或借助栈等辅助数据结构。

  • 链表操作的指针复杂性:在操作链表(如插入、删除)时,指针的使用容易导致错误,例如指针丢失、空指针访问等,增加了开发和调试的难度。

    单链表适合数据量动态变化、需要频繁插入和删除、但不要求随机访问的场景。

单链表结构图 

2.1.1 定义

    C单链表结构定义如下:

/** Singly-linked List definitions.*/
#define SLIST_HEAD(name, type)						\
struct name {								\struct type *slh_first;	/* first element */			\
}#define	SLIST_HEAD_INITIALIZER(head)					\{ NULL }#ifndef _WIN32
#define SLIST_ENTRY(type)						\
struct {								\struct type *sle_next;	/* next element */			\
}
#endif

2.1.2 访问方法

    libevent通过宏定义来访问单链表的数据成员及遍历:

/** Singly-linked List access methods.*/
#define	SLIST_FIRST(head)	((head)->slh_first)
#define	SLIST_END(head)		NULL
#define	SLIST_EMPTY(head)	(SLIST_FIRST(head) == SLIST_END(head))
#define	SLIST_NEXT(elm, field)	((elm)->field.sle_next)#define	SLIST_FOREACH(var, head, field)					\for((var) = SLIST_FIRST(head);					\(var) != SLIST_END(head);					\(var) = SLIST_NEXT(var, field))

2.1.3 操作函数 

    libevent实现的单链表操作主要有:初始化、中间插入、头部插入、尾部插入、头部移除:

/** Singly-linked List functions.*/
#define	SLIST_INIT(head) {						\SLIST_FIRST(head) = SLIST_END(head);				\
}#define	SLIST_INSERT_AFTER(slistelm, elm, field) do {			\(elm)->field.sle_next = (slistelm)->field.sle_next;		\(slistelm)->field.sle_next = (elm);				\
} while (0)#define	SLIST_INSERT_HEAD(head, elm, field) do {			\(elm)->field.sle_next = (head)->slh_first;			\(head)->slh_first = (elm);					\
} while (0)#define	SLIST_REMOVE_HEAD(head, field) do {				\(head)->slh_first = (head)->slh_first->field.sle_next;		\
} while (0)

    以上是SLIST单链表所有源码实现,相对简单,不赘述。

2.2 双向链表

    在libevent中,LIS链表是一种双向链表结构,其每个节点包含一个指向前后节点的指针。和单向链表不同,双向链表一般是用来表示事件的多重组织方式,即不同事件类型和优先级的事件链。下面是双向链表的优缺点:

优点

  • 层级化的事件管理:双向链表可以将事件按优先级、事件类型等不同维度进行分层组织,方便事件循环时按需分配处理,尤其在多优先级事件的场景中非常有效。

  • 便于按优先级调度:通过将高优先级事件和低优先级事件划分到不同链表层级中,处理事件时可以优先处理高优先级链表上的事件,便于实现调度控制,保证关键事件的及时性。

  • 插入、删除操作简便:双向链表特性使得在任意位置插入或删除节点非常高效,只需调整前后指针,时间复杂度为 O(1),适合频繁操作。

  • 事件遍历灵活:可以在多层链表中进行迭代,适合多种情况下的事件遍历、优先级切换等。对于复杂事件处理逻辑,可以快速遍历特定层级事件,提升事件处理效率。

缺点

  • 内存消耗较大:每个节点需要存储两个指针(指向前后节点),如果链表层级很多且节点较多,内存开销会明显增大。

  • 操作复杂性增加:双向链表的多层级设计,尤其是在事件分层组织、删除和调整优先级时,对开发者理解和调试的要求较高,可能导致复杂的指针操作错误。

  • 访问深层节点效率降低:如果事件在较低优先级的层级,处理时需要逐层遍历链表才能访问到这些事件,效率相对较低,适合优先级较为集中的场景。

  • 对随机访问支持差:链表结构本身不支持随机访问,查找特定事件或位置的节点时需要顺序遍历,在大规模节点的情况下效率较低。

双向链表结构图 

2.2.1 定义

/** List definitions.*/
#define LIST_HEAD(name, type)						\
struct name {								\struct type *lh_first;	/* first element */			\
}#define LIST_HEAD_INITIALIZER(head)					\{ NULL }#define LIST_ENTRY(type)						\
struct {								\struct type *le_next;	/* next element */			\struct type **le_prev;	/* address of previous next element */	\
}

 2.2.2 访问方法 

/** List access methods*/
#define	LIST_FIRST(head)		((head)->lh_first)
#define	LIST_END(head)			NULL
#define	LIST_EMPTY(head)		(LIST_FIRST(head) == LIST_END(head))
#define	LIST_NEXT(elm, field)		((elm)->field.le_next)#define LIST_FOREACH(var, head, field)					\for((var) = LIST_FIRST(head);					\(var)!= LIST_END(head);					\(var) = LIST_NEXT(var, field))

     2.2.3 操作函数

/** List functions.*/
#define	LIST_INIT(head) do {						\LIST_FIRST(head) = LIST_END(head);				\
} while (0)#define LIST_INSERT_AFTER(listelm, elm, field) do {			\if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)	\(listelm)->field.le_next->field.le_prev =		\&(elm)->field.le_next;				\(listelm)->field.le_next = (elm);				\(elm)->field.le_prev = &(listelm)->field.le_next;		\
} while (0)#define	LIST_INSERT_BEFORE(listelm, elm, field) do {			\(elm)->field.le_prev = (listelm)->field.le_prev;		\(elm)->field.le_next = (listelm);				\*(listelm)->field.le_prev = (elm);				\(listelm)->field.le_prev = &(elm)->field.le_next;		\
} while (0)#define LIST_INSERT_HEAD(head, elm, field) do {				\if (((elm)->field.le_next = (head)->lh_first) != NULL)		\(head)->lh_first->field.le_prev = &(elm)->field.le_next;\(head)->lh_first = (elm);					\(elm)->field.le_prev = &(head)->lh_first;			\
} while (0)#define LIST_REMOVE(elm, field) do {					\if ((elm)->field.le_next != NULL)				\(elm)->field.le_next->field.le_prev =			\(elm)->field.le_prev;				\*(elm)->field.le_prev = (elm)->field.le_next;			\
} while (0)#define LIST_REPLACE(elm, elm2, field) do {				\if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)	\(elm2)->field.le_next->field.le_prev =			\&(elm2)->field.le_next;				\(elm2)->field.le_prev = (elm)->field.le_prev;			\*(elm2)->field.le_prev = (elm2);				\
} while (0)

2.3 简单队列 

    libevent中的简单队列是一个由单链表来实现的。simple queue 是一种简单的队列数据结构,它通常用于按顺序存储和访问元素。队列是一种先进先出(FIFO, First In First Out)结构,元素总是从队尾插入,从队首删除。下面是 simple queue 的特点、基本操作和优缺点。

特点

  • FIFO(先进先出):最先进入队列的元素会最先被处理。
  • 单向操作:通常只允许在队尾插入元素、在队首删除元素,保持队列有序性。
  • 动态增长:可以在需要时动态添加元素,内存使用通常灵活。

优点

  • 操作简单:只需操作队尾和队首,插入和删除的复杂度为 O(1)。
  • 内存效率:链表实现的队列可以根据需要动态调整大小,适合存储不确定数量的数据。
  • 无锁并发:在一些并发场景中,可以通过特定设计使队列支持无锁操作,提高并发访问的效率。

缺点

  • 随机访问效率低:只能按顺序访问元素,无法高效地进行随机访问。
  • 可能有内存开销:链表实现的队列会在每个节点上存储额外的指针信息,在大量节点时会增加内存使用。
  • 阻塞问题:在生产者-消费者场景中,如果生产或消费速度不匹配,可能会导致队列过长或过短,需额外处理队列满或空的情况。 

 简单队列结构图

    2.3.1定义

/** Simple queue definitions.*/
#define SIMPLEQ_HEAD(name, type)					\
struct name {								\struct type *sqh_first;	/* first element */			\struct type **sqh_last;	/* addr of last next element */		\
}#define SIMPLEQ_HEAD_INITIALIZER(head)					\{ NULL, &(head).sqh_first }#define SIMPLEQ_ENTRY(type)						\
struct {								\struct type *sqe_next;	/* next element */			\
}

    2.3.2 访问方法

/** Simple queue access methods.*/
#define	SIMPLEQ_FIRST(head)	    ((head)->sqh_first)
#define	SIMPLEQ_END(head)	    NULL
#define	SIMPLEQ_EMPTY(head)	    (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
#define	SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)#define SIMPLEQ_FOREACH(var, head, field)				\for((var) = SIMPLEQ_FIRST(head);				\(var) != SIMPLEQ_END(head);					\(var) = SIMPLEQ_NEXT(var, field))

    2.3.3 操作函数

/** Simple queue functions.*/
#define	SIMPLEQ_INIT(head) do {						\(head)->sqh_first = NULL;					\(head)->sqh_last = &(head)->sqh_first;				\
} while (0)#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {			\if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)	\(head)->sqh_last = &(elm)->field.sqe_next;		\(head)->sqh_first = (elm);					\
} while (0)#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {			\(elm)->field.sqe_next = NULL;					\*(head)->sqh_last = (elm);					\(head)->sqh_last = &(elm)->field.sqe_next;			\
} while (0)#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\(head)->sqh_last = &(elm)->field.sqe_next;		\(listelm)->field.sqe_next = (elm);				\
} while (0)#define SIMPLEQ_REMOVE_HEAD(head, elm, field) do {			\if (((head)->sqh_first = (elm)->field.sqe_next) == NULL)	\(head)->sqh_last = &(head)->sqh_first;			\
} while (0)

2.4 尾队列

    TAILQ 是一种双向链表队列,广泛用于 BSD 系统和 libevent 库中。它支持从队列头部或尾部高效地插入、删除和访问元素,便于实现先进先出(FIFO)队列、双端队列等多种数据结构。TAILQ 的设计特点主要在于它使用双向链表维护队列顺序,尾部始终指向最后一个元素,因此插入和删除操作效率较高。

    相关操作在include/event2/event_struct.h文件下,如果对应平台有此<sys/queue.h>文件相应实现,则可直接用。

优点

  • 双端操作高效:TAILQ 支持头部和尾部的高效插入和删除操作,时间复杂度均为 O(1)。
  • 灵活性高:支持在任意位置插入和删除,适合实现各种类型的队列、链表、双端队列等。
  • 结构简洁:TAILQ 数据结构相对简单,内存开销较低,适合内核、系统编程等低资源场景。

缺点

  • 访问性能差:TAILQ 不支持随机访问,访问特定位置需要顺序遍历,效率较低。
  • 指针操作复杂:TAILQ 的双向指针结构在操作中容易出现指针悬挂、泄漏等错误,增加开发难度。

 尾队列结构图

    2.4.1 定义

/** Tail queue definitions.*/
#define TAILQ_HEAD(name, type)						\
struct name {								\struct type *tqh_first;	/* first element */			\struct type **tqh_last;	/* addr of last next element */		\
}#define TAILQ_HEAD_INITIALIZER(head)					\{ NULL, &(head).tqh_first }#define TAILQ_ENTRY(type)						\
struct {								\struct type *tqe_next;	/* next element */			\struct type **tqe_prev;	/* address of previous next element */	\
}

    2.4.2 方法访问

/** tail queue access methods*/
#define	TAILQ_FIRST(head)		((head)->tqh_first)
#define	TAILQ_END(head)			NULL
#define	TAILQ_NEXT(elm, field)		((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname)					\(*(((struct headname *)((head)->tqh_last))->tqh_last))
/* XXX */
#define TAILQ_PREV(elm, headname, field)				\(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define	TAILQ_EMPTY(head)						\(TAILQ_FIRST(head) == TAILQ_END(head))#define TAILQ_FOREACH(var, head, field)					\for((var) = TAILQ_FIRST(head);					\(var) != TAILQ_END(head);					\(var) = TAILQ_NEXT(var, field))#define TAILQ_FOREACH_REVERSE(var, head, headname, field)		\for((var) = TAILQ_LAST(head, headname);				\(var) != TAILQ_END(head);					\(var) = TAILQ_PREV(var, headname, field))

    2.4.3 操作函数

/** Tail queue functions.*/
#define	TAILQ_INIT(head) do {						\(head)->tqh_first = NULL;					\(head)->tqh_last = &(head)->tqh_first;				\
} while (0)#define TAILQ_INSERT_HEAD(head, elm, field) do {			\if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)	\(head)->tqh_first->field.tqe_prev =			\&(elm)->field.tqe_next;				\else								\(head)->tqh_last = &(elm)->field.tqe_next;		\(head)->tqh_first = (elm);					\(elm)->field.tqe_prev = &(head)->tqh_first;			\
} while (0)#define TAILQ_INSERT_TAIL(head, elm, field) do {			\(elm)->field.tqe_next = NULL;					\(elm)->field.tqe_prev = (head)->tqh_last;			\*(head)->tqh_last = (elm);					\(head)->tqh_last = &(elm)->field.tqe_next;			\
} while (0)#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {		\if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\(elm)->field.tqe_next->field.tqe_prev =			\&(elm)->field.tqe_next;				\else								\(head)->tqh_last = &(elm)->field.tqe_next;		\(listelm)->field.tqe_next = (elm);				\(elm)->field.tqe_prev = &(listelm)->field.tqe_next;		\
} while (0)#define	TAILQ_INSERT_BEFORE(listelm, elm, field) do {			\(elm)->field.tqe_prev = (listelm)->field.tqe_prev;		\(elm)->field.tqe_next = (listelm);				\*(listelm)->field.tqe_prev = (elm);				\(listelm)->field.tqe_prev = &(elm)->field.tqe_next;		\
} while (0)#define TAILQ_REMOVE(head, elm, field) do {				\if (((elm)->field.tqe_next) != NULL)				\(elm)->field.tqe_next->field.tqe_prev =			\(elm)->field.tqe_prev;				\else								\(head)->tqh_last = (elm)->field.tqe_prev;		\*(elm)->field.tqe_prev = (elm)->field.tqe_next;			\
} while (0)#define TAILQ_REPLACE(head, elm, elm2, field) do {			\if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)	\(elm2)->field.tqe_next->field.tqe_prev =		\&(elm2)->field.tqe_next;				\else								\(head)->tqh_last = &(elm2)->field.tqe_next;		\(elm2)->field.tqe_prev = (elm)->field.tqe_prev;			\*(elm2)->field.tqe_prev = (elm2);				\
} while (0)

2.4 圆形队列

    Circular Queue(循环队列)是一种队列数据结构,它将队列逻辑上视为“环形结构”,通过在固定大小的数组中实现循环,来避免普通线性队列出现的“假溢出”问题。循环队列在系统编程和实时系统中常用,如 CPU 调度、任务管理等。

优点

  • 内存效率高:循环队列的最大优势在于通过循环重用数组空间,避免了普通线性队列“假溢出”的问题。
  • 固定大小:在实时和嵌入式系统中,循环队列的固定数组大小有助于内存管理。

缺点

  • 容量限制:固定数组大小的循环队列有容量上限,超出限制会导致队列溢出。
  • 指针计算复杂:由于指针的循环移动,操作比单纯的线性队列复杂,可能会产生边界问题。

 环形队列结构图

2.4.1 定义

/** Circular queue definitions.*/
#define CIRCLEQ_HEAD(name, type)					\
struct name {								\struct type *cqh_first;		/* first element */		\struct type *cqh_last;		/* last element */		\
}#define CIRCLEQ_HEAD_INITIALIZER(head)					\{ CIRCLEQ_END(&head), CIRCLEQ_END(&head) }#define CIRCLEQ_ENTRY(type)						\
struct {								\struct type *cqe_next;		/* next element */		\struct type *cqe_prev;		/* previous element */		\
}

 2.4.2 访问方法

/** Circular queue access methods*/
#define	CIRCLEQ_FIRST(head)		((head)->cqh_first)
#define	CIRCLEQ_LAST(head)		((head)->cqh_last)
#define	CIRCLEQ_END(head)		((void *)(head))
#define	CIRCLEQ_NEXT(elm, field)	((elm)->field.cqe_next)
#define	CIRCLEQ_PREV(elm, field)	((elm)->field.cqe_prev)
#define	CIRCLEQ_EMPTY(head)						\(CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))#define CIRCLEQ_FOREACH(var, head, field)				\for((var) = CIRCLEQ_FIRST(head);				\(var) != CIRCLEQ_END(head);					\(var) = CIRCLEQ_NEXT(var, field))#define CIRCLEQ_FOREACH_REVERSE(var, head, field)			\for((var) = CIRCLEQ_LAST(head);					\(var) != CIRCLEQ_END(head);					\(var) = CIRCLEQ_PREV(var, fie

2.4.3 操作函数

/** Circular queue functions.*/
#define	CIRCLEQ_INIT(head) do {						\(head)->cqh_first = CIRCLEQ_END(head);				\(head)->cqh_last = CIRCLEQ_END(head);				\
} while (0)#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\(elm)->field.cqe_next = (listelm)->field.cqe_next;		\(elm)->field.cqe_prev = (listelm);				\if ((listelm)->field.cqe_next == CIRCLEQ_END(head))		\(head)->cqh_last = (elm);				\else								\(listelm)->field.cqe_next->field.cqe_prev = (elm);	\(listelm)->field.cqe_next = (elm);				\
} while (0)#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {		\(elm)->field.cqe_next = (listelm);				\(elm)->field.cqe_prev = (listelm)->field.cqe_prev;		\if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))		\(head)->cqh_first = (elm);				\else								\(listelm)->field.cqe_prev->field.cqe_next = (elm);	\(listelm)->field.cqe_prev = (elm);				\
} while (0)#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\(elm)->field.cqe_next = (head)->cqh_first;			\(elm)->field.cqe_prev = CIRCLEQ_END(head);			\if ((head)->cqh_last == CIRCLEQ_END(head))			\(head)->cqh_last = (elm);				\else								\(head)->cqh_first->field.cqe_prev = (elm);		\(head)->cqh_first = (elm);					\
} while (0)#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\(elm)->field.cqe_next = CIRCLEQ_END(head);			\(elm)->field.cqe_prev = (head)->cqh_last;			\if ((head)->cqh_first == CIRCLEQ_END(head))			\(head)->cqh_first = (elm);				\else								\(head)->cqh_last->field.cqe_next = (elm);		\(head)->cqh_last = (elm);					\
} while (0)#define	CIRCLEQ_REMOVE(head, elm, field) do {				\if ((elm)->field.cqe_next == CIRCLEQ_END(head))			\(head)->cqh_last = (elm)->field.cqe_prev;		\else								\(elm)->field.cqe_next->field.cqe_prev =			\(elm)->field.cqe_prev;				\if ((elm)->field.cqe_prev == CIRCLEQ_END(head))			\(head)->cqh_first = (elm)->field.cqe_next;		\else								\(elm)->field.cqe_prev->field.cqe_next =			\(elm)->field.cqe_next;				\
} while (0)#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {			\if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==		\CIRCLEQ_END(head))						\(head).cqh_last = (elm2);				\else								\(elm2)->field.cqe_next->field.cqe_prev = (elm2);	\if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==		\CIRCLEQ_END(head))						\(head).cqh_first = (elm2);				\else								\(elm2)->field.cqe_prev->field.cqe_next = (elm2);	\
} while (0)

2.5 哈希表

优点

  • 高效查询和插入:哈希表的设计使其查询插入的平均时间复杂度接近 O(1),在管理大量事件(如定时器事件)时非常高效。

  • 动态扩展:libevent 的哈希表可以根据负载因子动态扩展,降低碰撞率并保持性能。

  • 空间利用率高:哈希表通过散列函数分布元素,避免了链表或树结构的空间浪费,适合事件数量较多的场景。

缺点

  • 内存占用波动:在动态扩展过程中,哈希表需要重新分配内存,这可能导致内存使用不稳定。

  • 碰撞处理开销:尽管有较低的碰撞概率,但在高负载情况下,链表长度可能增加,导致操作复杂度退化 O(n)

  • 线程不安全:libevent 中的哈希表设计在多线程环境下不安全,使用时需要额外的锁机制来避免竞争。

    libevent在对IO事件和signal事件进行组织管理的时候,采用的哈希表,以IO事件为例,fd为哈希表的,哈希槽为evmap_io双向链表,详见下图:

IO事件哈希表 

libevent在对signal事件的组织管理方式,与IO事件基本是一样的,以signal为桶,哈希槽为evmap_signal双向链表,详见下图:

signal事件结构图 

3 小结

  • 本文主要对libevent的基本数据结构进行介绍,包括单链表、双向链表、简单队列、尾队列、圆形队列以及哈希表的实现原理;
  • 除了timer事件使用了小跟堆以外,libevent对event的组织和管理基本都可以用以上基本数据结构来组织,小跟堆将另起一文来加以介绍。

相关文章:

libevent源码剖析-基本数据结构

1 简介 前面系列文章对libevent源码的主体结构&#xff0c;从reactor框架实现&#xff0c;到evbuffer和bufferevent实现原理&#xff0c;及libevent的例子进行了剖析&#xff0c;自此&#xff0c;我们便可基于libevent开发app了。 从本文开始&#xff0c;主要来介绍下libevent源…...

往期文章汇总——射频测量+无线通信+软件无线电+6G科普

本节目录 一、射频测量系列往期链接 二、无线通信系列往期链接 三、软件无线电系列往期链接 四、6G科普系列往期链接本节内容 一、射频测量系列往期链接 射频测量 | 滤波器的关注指标 射频测量 | 射频电路中的负载与滤波器 射频测量 | 射频衰减器的功率系数 射频测量 | 衰减…...

微信小程序 - 深 / 浅拷贝实现方法,微信小程序深拷贝与浅拷贝,函数方法封装直接调用使用,深拷贝cloneDeep和浅拷贝clone(深复制和浅复制)

前言 在微信小程序中,你无法 直接使用常规浏览器环境中的深浅拷贝方法。 但可以借助 utils.js 实现,下面是方法。 创建深浅拷贝函数 依次打开小程序目录【utils】→【utils.js】,写入深拷贝函数并暴露出去。 // utils.js// 对象深拷贝函数 const deepClone = function(in…...

Log4Net配置详解及输出自定义消息类示例代码

1.简单使用实例 1.1 添加log4net.dll的引用。 在NuGet程序包中搜索log4net并添加&#xff0c;此次我所用版本为2.0.17。如下图&#xff1a; 1.2 添加配置文件 右键项目&#xff0c;添加新建项&#xff0c;搜索选择应用程序配置文件&#xff0c;命名为log4net.config&#xff0c…...

C++在实际项目中的应用第二节:C++与区块链

第五章&#xff1a;C在实际项目中的应用 第二课&#xff1a;C与区块链 区块链技术因其去中心化、不可篡改和透明性而受到广泛关注。在这门课程中&#xff0c;我们将深入探讨区块链的基本原理、智能合约的开发以及实际应用的案例分析&#xff0c;重点使用 C 作为实现语言&…...

浅记React面试丢人时刻

前提 去面试了&#xff0c;技术面完一轮之后&#xff0c;突发的来了一次React的考察&#xff0c;哥们&#xff0c;猝不及防之下&#xff0c;脑袋直接清空&#xff0c;啥也想不起来了。现在想想&#xff0c;实属丢人&#xff0c;记录一下啥也没答出来的面试&#xff0c;钉在耻辱…...

Python入门:学会Python装饰器让你的代码如虎添翼!(Python如何不改动原有函数代码添加一些额外的功能)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 什么是Python装饰器📝 如何编写Python装饰器📝 带参数的装饰器📝 Python装饰器的使用场景📝 注意事项📝 多装饰器的使用⚓️ 相关链接 ⚓️📖 介绍 📖 你是不是在写代码的时候,常常会想有没有…...

【C++】哈希冲突的解决办法:闭散列 与 开散列

哈希冲突解决 上一篇博客提到了&#xff0c;哈希函数的优化可以减小哈希冲突发生的可能性&#xff0c;但无法完全避免。本文就来探讨一下解决哈希冲突的两种常见方法&#xff1a;闭散列和开散列 1.闭散列 闭散列也叫开放定址法&#xff0c;发生哈希冲突时&#xff0c;如果哈…...

复刻系列-原神 5.1 版本先行展示页

复刻原神 5.1 版本先行展示页 0. 视频 BilBil站视频演示 复刻-原神5.1版本先行展示页 1. 基本信息 作者: 啊是特嗷桃系列: 复刻系列官方的网站: 《原神》官方网站-全新5.1版本「命定将焚的虹光」上线&#xff01;复刻的网站: 《原神》复刻网站-全新5.1版本「命定将焚的虹光」…...

STM32 第3章 如何用串口下载程序

时间:2024.10.28 一、学习内容 1、安装USB转串口驱动 1.1串口下载连接示意图 1、USB转串口模块在开发板上是一个独立的模块,可通过调帽与其他串口连接,USART1/2/3/4/5 2、只有USART1才具有串口下载的功能。 3、CH340是电平转换芯片,将电脑端输出的USB电平和单片机输…...

HT71782 20V,15A全集成同步升压转换器

1、特征 输入电压范围VN:2.7V-20V 输出电压范围VouT:4.5V-20V 可编程峰值电流:15A 高转换效率: 93%(VIN7.4V,VoUT15.5V,IouT 1.5A) 轻载条件下两种调制方式:脉频调制(PFM)和 强制脉宽调试(FPWM) 支持两种tr/t模式&#xff0c;应对EMI挑战 低关断功耗&#xff0c;关断电流1uA 可…...

[含文档+PPT+源码等]精品基于PHP实现的培训机构信息管理系统的设计与实现

基于PHP实现的培训机构信息管理系统的设计与实现背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、社会发展与教育需求 随着经济的不断发展和人口数量的增加&#xff0c;教育培训行业迎来了前所未有的发展机遇。家长对子女教育的重视程度日益提高&#xff0c;课外…...

亚信安全DeepSecurity中标知名寿险机构云主机安全项目

近日&#xff0c;亚信安全DeepSecurity成功中标国内知名寿险机构的云主机安全项目。亚信安全凭借在云主机安全防护领域的突出技术优势&#xff0c;结合安全运营的能力&#xff0c;以“实战化”为指导&#xff0c;为用户提供无惧威胁攻击、无忧安全运营的一站式云安全体系&#…...

论文解析八: GAN:Generative Adversarial Nets(生成对抗网络)

目录 1.GAN&#xff1a;Generative Adversarial Nets&#xff08;生成对抗网络&#xff09;1、标题 作者2、摘要 Abstract3、导言 IntroductionGAN的介绍 4、相关工作 Related work5、模型 Adversarial nets总结 6.理论计算 Theoretical Results具体算法公式全局优化 Global O…...

【ARM】ARM架构参考手册_Part B 内存和系统架构(2)

目录 2.1 关于系统控制协处理器 2.2 寄存器 2.1 关于系统控制协处理器 所有标准内存和系统设施都由协处理器15&#xff08;CP15&#xff09;控制&#xff0c;因此它被称为系统控制协处理器。有些设施也使用其他控制方法&#xff0c;这些方法在描述这些设施的章节中有描述。例…...

HttpServer模块 --- 封装TcpServer支持Http协议

目录 模块设计思想 模块代码实现 模块设计思想 本模块就是设计一个HttpServer模块&#xff0c;提供便携的搭建http协议的服务器的方法。 那么这个模块需要如何设计呢&#xff1f; 这还需要从Http请求说起。 首先http请求是分为静态资源请求和功能性请求的。 静态资源请求…...

蓝牙资讯|iOS 18.1 正式版下周推送,AirPods Pro 2耳机将带来助听器功能

苹果公司宣布将在下周发布 iOS 18.1 正式版&#xff0c;同时确认该更新将为 AirPods Pro 2 耳机带来新增“临床级”助听器功能。在启用功能后&#xff0c;用户首先需要使用 AirPods 和 iPhone 进行简短的听力测试&#xff0c;如果检测到听力损失&#xff0c;系统将创建一项“个…...

C语言之环形缓冲区概述及实现

在C语言中存在一种高效的数据结构&#xff0c;叫做环形缓存区&#xff0c;其被广泛用于处理数据流与缓存区的管理。如&#xff1a;数据的收发、程序层级之间的数据交换、硬件接收大量数据的场景&#xff0c;同时也可配合DMA实现通信协议收发数据&#xff0c;已确保流量控制、数…...

C++Socket通讯样例(服务端)

1. 创建Socket实例并开启。 private int OpenTcp(int port, string ip "") {//1. 开启服务端try{_tcpServer new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPAddress ipAddr IPAddress.Any;if (ip ! "" && i…...

【学术会议论文投稿】大数据治理:解锁数据价值,引领未来创新

第六届国际科技创新学术交流大会&#xff08;IAECST 2024&#xff09;_艾思科蓝_学术一站式服务平台 更多学术会议请看&#xff1a;https://ais.cn/u/nuyAF3 目录 引言 一、大数据治理的定义 二、大数据治理的重要性 三、大数据治理的核心组件 四、大数据治理的实践案例…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...