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源码的主体结构,从reactor框架实现,到evbuffer和bufferevent实现原理,及libevent的例子进行了剖析,自此,我们便可基于libevent开发app了。 从本文开始,主要来介绍下libevent源…...
往期文章汇总——射频测量+无线通信+软件无线电+6G科普
本节目录 一、射频测量系列往期链接 二、无线通信系列往期链接 三、软件无线电系列往期链接 四、6G科普系列往期链接本节内容 一、射频测量系列往期链接 射频测量 | 滤波器的关注指标 射频测量 | 射频电路中的负载与滤波器 射频测量 | 射频衰减器的功率系数 射频测量 | 衰减…...
微信小程序 - 深 / 浅拷贝实现方法,微信小程序深拷贝与浅拷贝,函数方法封装直接调用使用,深拷贝cloneDeep和浅拷贝clone(深复制和浅复制)
前言 在微信小程序中,你无法 直接使用常规浏览器环境中的深浅拷贝方法。 但可以借助 utils.js 实现,下面是方法。 创建深浅拷贝函数 依次打开小程序目录【utils】→【utils.js】,写入深拷贝函数并暴露出去。 // utils.js// 对象深拷贝函数 const deepClone = function(in…...
Log4Net配置详解及输出自定义消息类示例代码
1.简单使用实例 1.1 添加log4net.dll的引用。 在NuGet程序包中搜索log4net并添加,此次我所用版本为2.0.17。如下图: 1.2 添加配置文件 右键项目,添加新建项,搜索选择应用程序配置文件,命名为log4net.config,…...
C++在实际项目中的应用第二节:C++与区块链
第五章:C在实际项目中的应用 第二课:C与区块链 区块链技术因其去中心化、不可篡改和透明性而受到广泛关注。在这门课程中,我们将深入探讨区块链的基本原理、智能合约的开发以及实际应用的案例分析,重点使用 C 作为实现语言&…...
浅记React面试丢人时刻
前提 去面试了,技术面完一轮之后,突发的来了一次React的考察,哥们,猝不及防之下,脑袋直接清空,啥也想不起来了。现在想想,实属丢人,记录一下啥也没答出来的面试,钉在耻辱…...
Python入门:学会Python装饰器让你的代码如虎添翼!(Python如何不改动原有函数代码添加一些额外的功能)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 什么是Python装饰器📝 如何编写Python装饰器📝 带参数的装饰器📝 Python装饰器的使用场景📝 注意事项📝 多装饰器的使用⚓️ 相关链接 ⚓️📖 介绍 📖 你是不是在写代码的时候,常常会想有没有…...
【C++】哈希冲突的解决办法:闭散列 与 开散列
哈希冲突解决 上一篇博客提到了,哈希函数的优化可以减小哈希冲突发生的可能性,但无法完全避免。本文就来探讨一下解决哈希冲突的两种常见方法:闭散列和开散列 1.闭散列 闭散列也叫开放定址法,发生哈希冲突时,如果哈…...
复刻系列-原神 5.1 版本先行展示页
复刻原神 5.1 版本先行展示页 0. 视频 BilBil站视频演示 复刻-原神5.1版本先行展示页 1. 基本信息 作者: 啊是特嗷桃系列: 复刻系列官方的网站: 《原神》官方网站-全新5.1版本「命定将焚的虹光」上线!复刻的网站: 《原神》复刻网站-全新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模式,应对EMI挑战 低关断功耗,关断电流1uA 可…...
[含文档+PPT+源码等]精品基于PHP实现的培训机构信息管理系统的设计与实现
基于PHP实现的培训机构信息管理系统的设计与实现背景,可以从以下几个方面进行阐述: 一、社会发展与教育需求 随着经济的不断发展和人口数量的增加,教育培训行业迎来了前所未有的发展机遇。家长对子女教育的重视程度日益提高,课外…...
亚信安全DeepSecurity中标知名寿险机构云主机安全项目
近日,亚信安全DeepSecurity成功中标国内知名寿险机构的云主机安全项目。亚信安全凭借在云主机安全防护领域的突出技术优势,结合安全运营的能力,以“实战化”为指导,为用户提供无惧威胁攻击、无忧安全运营的一站式云安全体系&#…...
论文解析八: GAN:Generative Adversarial Nets(生成对抗网络)
目录 1.GAN:Generative Adversarial Nets(生成对抗网络)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(CP15)控制,因此它被称为系统控制协处理器。有些设施也使用其他控制方法,这些方法在描述这些设施的章节中有描述。例…...
HttpServer模块 --- 封装TcpServer支持Http协议
目录 模块设计思想 模块代码实现 模块设计思想 本模块就是设计一个HttpServer模块,提供便携的搭建http协议的服务器的方法。 那么这个模块需要如何设计呢? 这还需要从Http请求说起。 首先http请求是分为静态资源请求和功能性请求的。 静态资源请求…...
蓝牙资讯|iOS 18.1 正式版下周推送,AirPods Pro 2耳机将带来助听器功能
苹果公司宣布将在下周发布 iOS 18.1 正式版,同时确认该更新将为 AirPods Pro 2 耳机带来新增“临床级”助听器功能。在启用功能后,用户首先需要使用 AirPods 和 iPhone 进行简短的听力测试,如果检测到听力损失,系统将创建一项“个…...
C语言之环形缓冲区概述及实现
在C语言中存在一种高效的数据结构,叫做环形缓存区,其被广泛用于处理数据流与缓存区的管理。如:数据的收发、程序层级之间的数据交换、硬件接收大量数据的场景,同时也可配合DMA实现通信协议收发数据,已确保流量控制、数…...
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…...
【学术会议论文投稿】大数据治理:解锁数据价值,引领未来创新
第六届国际科技创新学术交流大会(IAECST 2024)_艾思科蓝_学术一站式服务平台 更多学术会议请看:https://ais.cn/u/nuyAF3 目录 引言 一、大数据治理的定义 二、大数据治理的重要性 三、大数据治理的核心组件 四、大数据治理的实践案例…...
location中href和replace的区别
1.有两种方式: a、使用 location.href:window.location.href“success.html”; b、使用location.replace:window.location.replace(“new_file.html”); 2.区别是什么? 结果:href相当于打开一个新页面,…...
基于Spring Boot的在线摄影工作室开发指南
1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理网上摄影工作室的相关信息成为必然。开发合…...
JDK源码系列(五)—— ConcurrentHashMap + CAS 原理解析
更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 ConcurrentHashMap 类 ConcurrentHashMap 1.7 在JDK1.7中ConcurrentHashMap采用了数组分段锁的方式实现。 Segment(分段锁)-减少锁的粒度 ConcurrentHashMap中的分段锁称为Segment,它即类似于…...
技术成神之路:二十三种设计模式(导航页)
设计原则/模式链接面向对象的六大设计原则技术成神之路:面向对象的六大设计原则创建型模式单例模式建造者模式原型模式工厂方法模式抽象工厂模式行为型模式策略模式状态模式责任链模式观察者模式备忘录模式迭代器模式模板方法模式访问者模式中介者模式命令模式解释器…...
Rust编程与项目实战-元组
【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 8.2.1 元组的定义 元组是Rust的内置复合数据类型。Rust支持元组,而且元…...
容性串扰和感性串扰
串扰根源在于耦合,电场耦合产生容性耦合电流,磁场耦合产生感性耦合电流 关于容性后向串扰电压与后向串扰系数推导...
windows Terminal 闪退 -- 捣蛋砖家
最近点击Windows 终端总是闪退。 日志提示: 错误应用程序名称: WindowsTerminal.exe,版本: 1.21.2410.17001,时间戳: 0x67118f02 错误模块名称: ucrtbase.dll,版本: 10.0.22621.3593,时间戳: 0x10c46e71 异常代码: 0xc0000409 错…...
java-web-day5
1.spring-boot-web入门 目标: 开始最基本的web应用的构建 使用浏览器访问后端, 后端给浏览器返回HelloController 流程: 1.创建springboot工程, 填写模块信息, 并勾选web开发的相关依赖 注意: 在新版idea中模块创建时java下拉框只能选17, 21, 23 这里选17, maven版本是3.6.3, 很…...
Python | Leetcode Python题解之第508题出现次数最多的子树元素和
题目: 题解: class Solution:def findFrequentTreeSum(self, root: TreeNode) -> List[int]:cnt Counter()def dfs(node: TreeNode) -> int:if node is None:return 0sum node.val dfs(node.left) dfs(node.right)cnt[sum] 1return sumdfs(r…...
Java 分布式缓存
在当今的大规模分布式系统中,缓存技术扮演着至关重要的角色。Java 作为一种广泛应用的编程语言,拥有丰富的工具和框架来实现分布式缓存。本文将深入探讨 Java 分布式缓存的概念、优势、常见技术以及实际应用案例,帮助读者更好地理解和应用这一…...
wordpress 高亮代码/seo指的是什么意思
引言互联网时代,信息传输的基础媒介是比特流,即承载着各种有效信息的01串。换句话说,我们在手机上或者电脑上看到的各类媒体信息,例如文字信息、图片信息亦或是视频信息,其根源上都是一些由二进制的0和1组成的比特流。…...
美国一级a做爰片免费网站/音乐接单推广app平台
这几天修真院又又又迎来了一位退伍的兵哥哥,所以在征得兵哥哥的允许后,我们决定把他的转行历程分享出来,供大家参考参考。 这位兵哥哥呢姓王,我们就暂时称呼他为王大锤同学吧。 大锤同学在来到修真院之前呢,已经在家颓…...
定制网站开发报价/app推广赚钱平台
Java Q&A: 使用Factory Method模式 (转)[more]Java Q&A: 使用Factory Method模式Q: 阅读 "Polymorphism in its purest form" 一文时,我看到了一个不熟悉的术语 "Factory method"。你能解释一下什么是Factory method并说明如何使用它吗…...
深圳建设工程交易网站官网/百度广告大全
题意:给了一个矩阵图,要求使用回路把图中的树全部吃掉的方案树,没有树的点不能走,吃完了这个点也就没有了,走到哪吃到哪 用插头dp搞 #include <iostream> #include <algorithm> #include <cstdio> #include <…...
建设银行网站的目的是什么/关键词词库
数据结构中的栈不要与 Java 中的栈混淆,他们俩不是一回事,数据结构中的栈是一种受限制的线性表,栈具有先进后出、后进先出的特点,因为栈只允许访问最后一个数据项,即最后插入的数据项。也许你会有疑问,栈既…...
上海单位建设报建网站/网站文章优化技巧
在家装设计当中,卫生间的设计非常重要,因为它和我们联系十分紧密,而且使用也极其频繁,如果设计不过关的话,会极大降低我们的日常体验,在设计卫生间的时候,卫浴产品选择非常关键,因为…...