linux的时间轮
时间轮:高效管理海量定时任务的利器
1. 引言:为什么需要时间轮?
在许多应用场景中,我们都需要管理大量的定时任务,例如:
- 网络连接的超时检测。
- 分布式系统中的心跳检测。
- 缓存条目的过期淘汰。
- 需要延迟执行的操作(如 Kafka 中的延迟消息)。
- 游戏服务器中的 buff/debuff 持续时间。
传统的定时任务实现方式,如 Java 中的 Timer
类或 ScheduledThreadPoolExecutor
,通常基于优先队列 (Priority Queue) 或延迟队列 (DelayQueue)。当任务数量较少时,这些方法表现良好。然而,当需要管理的定时任务数量达到成千上万甚至数百万级别时,这些基于排序或堆结构实现的方案性能会急剧下降:
PriorityQueue
/DelayQueue
: 添加任务和获取到期任务的操作时间复杂度通常为 O(log N),其中 N 是任务数量。当 N 非常大时,每次操作的开销变得不可忽视。ScheduledThreadPoolExecutor
: 虽然内部优化较好,但其底层的DelayQueue
仍然存在 O(log N) 的瓶颈。
为了解决大规模定时任务管理带来的性能瓶颈,时间轮 (Time Wheel / Timing Wheel) 应运而生。它是一种借鉴了现实生活中时钟运作原理的数据结构,能够以接近 O(1) 的时间复杂度(均摊)实现任务的添加和驱动,极大地提高了定时任务管理的效率和可扩展性。
2. 时间轮的核心概念
想象一个钟表,它有秒针、分针和时针。时间轮就是这个概念的数字化实现。
- 轮盘 (Wheel): 时间轮的核心是一个循环数组,就像钟表的刻度盘。数组的每个元素被称为一个槽位 (Slot)。
- 槽位 (Slot): 每个槽位通常存储一个链表或集合,用于存放将在该时间刻度到期的定时任务。
- 滴答间隔 (Tick Duration): 时间轮指针(当前时间)向前移动一格(一个槽位)所代表的时间长度。这是时间轮的基本时间单位或精度。
- 轮盘大小 (Ticks Per Wheel / Wheel Size): 一个轮盘包含的总槽位数。
- 时间跨度 (Interval): 一个完整的轮盘所能表示的总时间范围,等于
滴答间隔 * 轮盘大小
。 - 当前时间指针 (Current Time Pointer): 指向轮盘中代表当前时间的那个槽位。随着时间的推移,指针周期性地在轮盘上移动。
- 圈数 (Round / Cycle): 当一个任务的延迟时间超过了轮盘的总时间跨度时,我们需要记录这个任务需要等待指针走过多少个整圈才能到期。
- 层级时间轮 (Hierarchical Time Wheel): 为了处理跨度非常大的定时任务(例如几天甚至几个月后到期的任务),同时避免单个轮盘过大导致内存浪费,可以设计多层时间轮。类似于钟表的秒针、分针、时针,底层轮盘转动一整圈会触发上一层轮盘前进一格。每一层轮盘可以有不同的
滴答间隔
和轮盘大小
。
工作原理简述:
- 添加任务: 当一个新任务需要被调度时,根据其延迟时间 (Delay) 和时间轮的
滴答间隔
,计算出它应该被放入哪个槽位以及需要等待的圈数。到期总滴答数 = (当前时间 + 延迟时间) / 滴答间隔
目标槽位 = 到期总滴答数 % 轮盘大小
所需圈数 = 到期总滴答数 / 轮盘大小
(如果是单层时间轮,通常是相对于当前指针位置计算圈数)- 将任务(包含其圈数信息)添加到
目标槽位
的链表中。
- 驱动时间轮: 一个独立的计时线程 (Ticker) 以固定的
滴答间隔
周期性地唤醒。- 计时线程将
当前时间指针
向前移动一个槽位。 - 检查并处理
当前时间指针
指向的槽位中的所有任务:- 对于单层时间轮:检查任务的
圈数
是否为 0。如果为 0,则表示任务到期,将其取出并执行(通常是提交到业务线程池执行,避免阻塞计时线程)。如果圈数
大于 0,则将其圈数
减 1,任务继续留在该槽位等待下一圈。 - 对于层级时间轮:处理当前槽位中
圈数
为 0 的任务。如果当前轮盘指针完成了一整圈的转动,则触发上一层轮盘前进一格,并可能将上一层到期的任务“降级”到当前轮盘重新计算槽位。
- 对于单层时间轮:检查任务的
- 计时线程将
- 取消任务: 需要根据任务的唯一标识,找到它所在的槽位和链表中的具体位置,然后将其移除。这通常需要额外的辅助数据结构(如
HashMap
)来快速定位任务。
3. 时间轮的实现细节
3.1 数据结构
一个基础的单层时间轮通常包含:
// 伪代码示例
class SimpleTimeWheel {Object[] wheel; // 循环数组,存储槽位int wheelSize; // 轮盘大小long tickDurationMs; // 滴答间隔 (毫秒)volatile long currentTimeMs; // 当前时间轮的时间戳int currentTickIndex; // 当前指针指向的槽位索引Executor taskExecutor; // 用于执行到期任务的线程池// 槽位,通常用链表或Set实现// 每个元素是一个 TimerTask 实例列表// List<TimerTask>[] slots;// 定时任务结构class TimerTask {long expirationMs; // 精确的到期时间戳int rounds; // 剩余圈数Runnable taskLogic; // 任务逻辑// 可能还需要 prev/next 指针用于链表操作,以及状态(如 CANCELLED)}
}
3.2 重要函数与参数
addTimer(taskLogic, delayMs)
: 添加定时任务- 计算到期时间戳:
expirationMs = currentTimeMs + delayMs
- 检查延迟: 如果
delayMs <= 0
或小于tickDurationMs
,可能立即执行或至少在下一个 tick 执行。 - 计算总滴答数:
totalTicks = delayMs / tickDurationMs
(注意整除可能带来的精度损失) - 计算所需圈数:
rounds = totalTicks / wheelSize
- 计算目标槽位:
targetSlotIndex = (currentTickIndex + totalTicks) % wheelSize
- 创建
TimerTask
: 封装taskLogic
,expirationMs
,rounds
等信息。 - 放入槽位: 将
TimerTask
添加到wheel[targetSlotIndex]
对应的链表/集合中。如果需要支持取消,可能需要将任务及其位置信息存入一个 Map。
- 计算到期时间戳:
advanceClock()
: 驱动时钟前进- 这个函数由内部的计时线程按
tickDurationMs
的间隔调用。 - 更新当前时间:
currentTimeMs += tickDurationMs
- 移动指针:
currentTickIndex = (currentTickIndex + 1) % wheelSize
- 处理当前槽位:
- 获取
wheel[currentTickIndex]
中的任务列表。 - 遍历列表中的每个
TimerTask
:- 如果任务已被取消,跳过/移除。
- 如果
task.rounds > 0
,将task.rounds
减 1。 - 如果
task.rounds == 0
:- 检查精确时间戳
task.expirationMs <= currentTimeMs
(可选,增加精确性)。 - 将任务从槽位列表中移除。
- 将
task.taskLogic
提交给taskExecutor
执行。
- 检查精确时间戳
- 处理层级晋升/降级 (如果是层级时间轮)。
- 获取
- 这个函数由内部的计时线程按
removeTimer(task)
: 取消定时任务- 需要一种机制来快速找到任务。
- 方案一:
addTimer
时,将任务对象映射到其所在的槽位索引和链表节点。removeTimer
时,通过 Map 找到位置并移除。 - 方案二:
TimerTask
自身维护一个状态 (e.g.,CANCELLED
)。advanceClock
处理任务时检查此状态,如果是CANCELLED
则直接移除。
- 方案一:
- 需要一种机制来快速找到任务。
3.3 关键参数详解
tickDuration
(滴答间隔):- 意义: 决定了时间轮的精度和计时线程的唤醒频率。
- 影响:
- 较小: 精度高,任务触发时间更接近预期;但计时线程唤醒更频繁,CPU 开销增大;相同时间跨度需要更多槽位或导致圈数增加。
- 较大: 精度低,任务触发时间可能有较大延迟 (最多一个
tickDuration
);计时线程负担小;可以用较少槽位覆盖较长时间。
- 权衡: 需要根据业务对定时精度的要求和系统资源进行选择。通常选择一个可接受的最小误差范围,如 1ms, 10ms, 100ms, 1s。
ticksPerWheel
/wheelSize
(轮盘大小):- 意义: 单个轮盘包含的槽位数,决定了单轮的时间跨度 (
tickDuration * wheelSize
)。 - 影响:
- 较小: 内存占用少;但单轮时间跨度短,较长延迟的任务需要计算很高的圈数,或者需要更多层级的轮盘。
- 较大: 内存占用大 (数组大小和可能的空槽位);单轮时间跨度长,可以减少任务的圈数或层级轮盘的需求。
- 权衡: 需要在内存消耗和处理长延迟任务的复杂度之间找到平衡。通常选择一个合适的值,如 64, 128, 256, 512。在 Netty 的
HashedWheelTimer
中,为了优化取模运算,强制要求wheelSize
是 2 的幂次方,这样可以用位运算& (wheelSize - 1)
代替取模% wheelSize
。
- 意义: 单个轮盘包含的槽位数,决定了单轮的时间跨度 (
currentTime
(当前时间):- 意义: 时间轮内部维护的逻辑时间。
- 影响: 所有任务的到期计算都基于这个时间。它的准确推进是时间轮正常工作的核心。
4. 工作流程图 (Mermaid 语法)
graph TDsubgraph 添加任务 (addTimer)A[开始: addTimer(task, delay)] --> B{计算 expiration, totalTicks, rounds, targetSlot};B --> C[创建 TimerTask (含 rounds)];C --> D[将 TimerTask 加入 wheel[targetSlot] 的链表];D --> E[结束];endsubgraph 驱动时钟 (advanceClock by Ticker Thread)F[开始: advanceClock()] --> G{currentTickIndex = (currentTickIndex + 1) % wheelSize};G --> H{获取 wheel[currentTickIndex] 的任务列表 L};H --> I{遍历 L 中的每个 task T};I -- T 未取消 --> J{T.rounds > 0 ?};J -- Yes --> K[T.rounds--];K --> L[任务保留在槽位];J -- No --> M{T.rounds == 0};M -- Yes --> N[从 L 中移除 T];N --> O[提交 T.taskLogic 到执行器];O --> P[下一个任务或结束遍历];M -- No --> P;I -- T 已取消 --> Q[从 L 中移除 T];Q --> P;P --> R{列表遍历完成?};R -- No --> I;R -- Yes --> S[结束 advanceClock];end
(注意:Mermaid 图需要在支持它的 Markdown 查看器中才能正确渲染。)
文字描述流程:
- 添加任务: 收到任务和延迟 -> 计算到期滴答数、圈数、目标槽位 -> 创建任务对象 -> 放入目标槽位的列表。
- 驱动时钟: 定时器触发 -> 当前指针移动到下一槽位 -> 获取该槽位任务列表 -> 遍历任务:
- 若任务已取消 -> 移除。
- 若任务圈数 > 0 -> 圈数减 1。
- 若任务圈数 == 0 -> 任务到期 -> 移除任务 -> 提交给执行线程池。
5. 测试用例
- Case 1: 基本添加与到期
- 操作:
tick=1ms
,size=8
. 添加一个延迟5ms
的任务 T1。 - 预期: 调用
advanceClock
5 次后,T1 应该在第(currentTickIndex + 5) % 8
个槽位被取出并执行。
- 操作:
- Case 2: 跨圈任务
- 操作:
tick=1ms
,size=8
. 添加一个延迟10ms
的任务 T2。 - 预期: T2 会被放入
(currentTickIndex + 10) % 8
即(currentTickIndex + 2) % 8
的槽位,rounds
为 1。advanceClock
2 次后指针到达该槽位,T2 的rounds
减为 0。再过8ms
(总共10ms
),指针再次到达该槽位,T2 被取出执行。
- 操作:
- Case 3: 同一槽位多任务
- 操作:
tick=1ms
,size=8
. 添加延迟3ms
的任务 T3 和 T4。 - 预期: T3 和 T4 都在
(currentTickIndex + 3) % 8
槽位的列表中。advanceClock
3 次后,T3 和 T4 都应被取出执行。
- 操作:
- Case 4: 任务取消
- 操作:
tick=1ms
,size=8
. 添加延迟6ms
的任务 T5。在advanceClock
调用 4 次后,取消 T5。 - 预期:
advanceClock
再调用 2 次到达 T5 所在槽位时,T5 不应被执行,可能被直接移除。
- 操作:
- Case 5: 零延迟或近即时任务
- 操作:
tick=1ms
,size=8
. 添加延迟0ms
或0.5ms
的任务 T6。 - 预期: T6 应该在下一次
advanceClock
调用时(即1ms
后)立即在当前指针的下一个槽位被执行。
- 操作:
- Case 6: 边界条件 (大延迟,层级轮)
- 操作: (假设有层级轮) 添加一个延迟远超单轮跨度的任务 T7。
- 预期: T7 最初被放入较高层级的轮盘。随着时间推进,它会逐层“降级”到较低层级的轮盘,最终在底层轮盘的正确槽位按时执行。
- Case 7: 精度测试
- 操作:
tick=100ms
,size=64
. 添加延迟150ms
的任务 T8。 - 预期: T8 会在
advanceClock
调用 2 次后 (即200ms
时) 执行,触发时间相对于预期有50ms
的延迟。
- 操作:
6. 开源项目中的时间轮实现
多个知名的开源项目都内置了高效的时间轮实现:
- Netty (HashedWheelTimer):
- 实现: 单层时间轮,
wheelSize
必须是 2 的幂次方以使用位运算优化取模。使用MpscQueue
(多生产者单消费者队列) 来安全地在业务线程和 Timer 线程间传递任务。任务执行默认由 Timer 线程自己完成,但可配置外部执行器。 - 意义: 在高性能网络编程领域广泛应用,用于处理大量连接的超时管理 (读/写超时、空闲检测)。其设计简洁高效,是学习时间轮实现的经典范例。 [1]
- 实现: 单层时间轮,
- Kafka (TimingWheel):
- 实现: 层级时间轮 (Hierarchical Timing Wheel)。Kafka 使用它来管理各种延迟操作,如延迟生产请求、延迟拉取请求、会话超时、事务超时等。它有一个底层的
DelayQueue
来驱动最高层时间轮的指针移动。任务可以在层级间升降。 [2] - 意义: Kafka 作为一个高吞吐量的分布式消息系统,其内部大量依赖精确且高效的定时调度。层级时间轮是支撑其实现这些功能的关键组件,保证了即使有海量延迟任务也能维持高性能。 [2]
- 实现: 层级时间轮 (Hierarchical Timing Wheel)。Kafka 使用它来管理各种延迟操作,如延迟生产请求、延迟拉取请求、会话超时、事务超时等。它有一个底层的
- Akka (TimerScheduler):
- 实现: Akka Actor 框架内部也使用了类似时间轮的机制 (具体实现可能演变,但核心思想一致) 来为 Actor 提供高效的
scheduleOnce
和scheduleAtFixedRate
等定时消息发送功能。 - 意义: 为基于 Actor 模型的并发编程提供了内建的、高性能的定时器服务,简化了在 Actor 中实现超时、重试、定时触发等逻辑。
- 实现: Akka Actor 框架内部也使用了类似时间轮的机制 (具体实现可能演变,但核心思想一致) 来为 Actor 提供高效的
- Dubbo:
- 实现: Dubbo 框架在其
FailTimer
等模块中也借鉴或使用了时间轮 (例如 Netty 的HashedWheelTimer
) 来管理失败请求的重试间隔、连接检测等。 - 意义: 提升了 Dubbo 在处理网络通信和故障恢复时的效率。
- 实现: Dubbo 框架在其
- ZooKeeper:
- 实现: ZooKeeper 服务器端使用时间轮 (
SessionTrackerImpl
内部) 来管理客户端会话的超时。 - 意义: 对于需要维护大量客户端连接状态的 ZooKeeper 来说,高效的会话超时管理至关重要,时间轮提供了很好的支持。
- 实现: ZooKeeper 服务器端使用时间轮 (
这些开源项目的实践证明了时间轮在处理大规模、高性能定时调度场景下的有效性和优越性。
7. 时间轮的优缺点
7.1 优点
- 高效性: 添加任务和驱动时钟前进(处理一个槽位的任务)的平均时间复杂度接近 O(1)。相比于 O(log N) 的
PriorityQueue
,在任务量巨大时优势明显。 - 可扩展性: 能够轻松管理数百万级别的定时任务而不会出现显著的性能下降。
- 实现相对简单: 尤其是单层时间轮,核心逻辑比较直观。
7.2 缺点
- 精度限制: 时间轮的精度受限于
tickDuration
。任务的实际触发时间可能比预期延迟最多一个tickDuration
的时间。不适用于对时间精度要求极高(如纳秒级)的场景。 - 内存消耗: 如果
wheelSize
很大,或者为了高精度而设置了非常小的tickDuration
(导致需要很大的wheelSize
或很多层级),可能会占用较多内存,即使很多槽位是空的。 - 任务执行阻塞风险: 如果到期任务的执行逻辑本身耗时较长,并且是在时间轮的 Timer 线程中直接执行,那么会阻塞后续的
advanceClock
调用,影响其他任务的准时性。通常需要将任务提交到独立的业务线程池中执行。 - 空推进: 即使当前槽位没有任何任务到期,
advanceClock
仍然需要执行(移动指针),这带来了一定的固定开销。
8. 总结
时间轮是一种为解决海量定时任务调度难题而设计的、高效的数据结构。它通过模拟时钟的运作机制,利用循环数组和链表(或其他集合),实现了近乎 O(1) 的任务添加和驱动效率。虽然存在精度限制,但在网络编程、分布式系统、消息队列等需要管理大量定时事件且对精度要求不是极端苛刻的场景下,时间轮展现出了巨大的优势。Netty、Kafka 等成熟开源项目的广泛应用,进一步证明了其价值和在高性能系统中的重要地位。理解时间轮的原理和权衡,有助于我们在设计相关系统时做出更优的技术选型。
相关文章:
linux的时间轮
时间轮:高效管理海量定时任务的利器 1. 引言:为什么需要时间轮? 在许多应用场景中,我们都需要管理大量的定时任务,例如: 网络连接的超时检测。分布式系统中的心跳检测。缓存条目的过期淘汰。需要延迟执行…...
《操作系统真象还原》第十二章(2)——进一步完善内核
文章目录 前言可变参数的原理实现系统调用write更新syscall.h更新syscall.c更新syscall-init.c 实现printf编写stdio.h编写stdio.c 第一次测试main.cmakefile结果截图 完善printf修改main.c 结语 前言 上部分链接:《操作系统真象还原》第十二章(1&#…...
MIT6.S081-lab8前置
MIT6.S081-lab8前置 注:本部分除了文件系统还包含了调度的内容。 调度 调度涉及到保存寄存器,恢复寄存器,就这一点而言,和我们的 trap 很像,但是实际上,我们实现并不是复用了 trap 的逻辑,我…...
Java从入门到精通 - Java语法
Java 语法 此笔记参考黑马教程,仅学习使用,如有侵权,联系必删 文章目录 Java 语法01 变量详解1. 变量里的数据在计算机中的存储原理1.1 二进制1.2 十进制转二进制的算法1.3 计算机中表示数据的最小单元总结1.4 字符在计算机中是如何存储的呢…...
【CF】Day50——Codeforces Round 960 (Div. 2) BCD
B. Array Craft 题目: 思路: 有点意思的构造 首先题目告诉我们 y < x,这是一个重要的条件 我们先来考虑简单情况,假如可以放0进去,那么我们只需要在 y ~ x 之间全放 1 ,其余都是 0 即可,但…...
MySQL 日期加减函数详解
MySQL 日期加减函数详解 1. DATE_ADD 函数 基本语法 DATE_ADD(date, INTERVAL expr unit)功能 在指定日期/时间上添加一个时间间隔 参数说明 date:要处理的日期/时间值(可以是DATE, DATETIME或TIMESTAMP类型)expr:要添加的间…...
NV189NV195美光固态闪存NV197NV199
NV189NV195美光固态闪存NV197NV199 在存储技术持续迭代的2025年,美光固态闪存NV189、NV195、NV197、NV199系列凭借其差异化的性能定位,正在重新定义数据存储的边界。本文将从技术参数、场景适配、行业价值等维度,为不同领域的专业人士提供深度…...
C语言-回调函数
回调函数 通过函数指针调用函数,而这个被调用的函数称为回调函数 回调函数是C语言中一种强大的机制,允许将函数作为参数传递给其他函数,从而在特定时机由后者调用。它的核心在于函数指针的使用 以下是回调函数的使用例子 先创建好一个函数…...
启发式算法-蚁群算法
蚁群算法是模拟蚂蚁觅食行为的仿生优化算法,原理是信息素的正反馈机制,蚂蚁通过释放信息素来引导同伴找到最短路径。把问题的元素抽象为多条路径,每次迭代时为每只蚂蚁构建一个解决方案,该解决方案对应一条完整的路径,…...
DeepSeek与MySQL:开启数据智能新时代
目录 一、引言:技术融合的力量二、DeepSeek 与 MySQL:技术基石2.1 DeepSeek 技术探秘2.2 MySQL 数据库深度解析 三、DeepSeek 与 MySQL 集成:从理论到实践3.1 集成原理剖析3.2 集成步骤详解 四、应用案例:实战中的价值体现4.1 电商…...
Modbus 通讯协议(超详细,简单易懂)
目录 一、协议中的寄存器定义 二、协议概述 三、使用串口的Modbus 报文帧 编辑 3.1、Modbus ASCII 模式 3.2、Modbus RTU 模式 3.3、功能码概要 3.4、Modbus 报文分析 四、什么是RS-485 RS-232? 一、协议中的寄存器定义 阅读 Modbus 协议时会发现它的概念别扭…...
单细胞测序试验设计赏析(一)
单细胞测序试验设计赏析(一) 单细胞测序试验设计中,单细胞测序技术通常会结合其它的技术来共同说明问题,或者结合年龄、性别等临床数据,进行分层分析说明问题以下以发表文章来进行一定的分析。 Single-cell RNA seque…...
ES6入门---第二单元 模块三:对象新增、
一:对象简洁语法: 1、变量简洁 <script>let name Strive;let age 18;let json {name, //name:name,age //age:age};console.log(json);</script> 2、函数简洁 let json {name, //name:name,age, //age:age/* showA:functi…...
多元随机变量协方差矩阵
主要记录多元随机变量数字特征相关内容。 关键词:多元统计分析 二元随机变量(X, Y) 说明:可以理解变量中的 X为身高、Y为体重 总体协方差 σ X Y c o v ( X , Y ) E [ ( X − μ X ) ( Y − μ Y ) ] E ( X Y ) − μ X μ Y \sigma_{XY}cov(X, Y)E[…...
计算机网络-同等学力计算机综合真题及答案
计算机网络-同等学力计算机综合真题及答案 (2003-2024) 2003 年网络 第二部分 计算机网络(共 30 分) (因大纲变动因此 2004 年真题仅附真题,不作解析。) 一、填空题(共 10 分&#…...
[案例二] 菜单条制作(Menuscript)与工具条制作(Toolbar)
最近五一正好毕业论文盲审,抽时间研究一下菜单条制作(Menuscript)与工具条制作(Toolbar)的制作,在NX二次开发中唐康林老师已经讲的很详细了,在这里只对视频中的内容进行总结,并且根据自己的想法进行补充。在里海博主的直播教学中发现一个很有趣的NX图标工具,本人大概做了一…...
bellard.org : QuickJS 如何使用 qjs 执行 js 脚本
参阅上一篇:Fabrice Bellard(个人网站:bellard.org)介绍 Fabrice Bellard(个人网站:bellard.org)是计算机领域最具影响力的程序员之一,其贡献跨越多个技术领域并持续推动开…...
计组复习笔记 3
前言 继续做例题。昨天做到第一个就把我难住了。可恶。 4.1 地址码越长,操作码越短。因为两者加起来是指令字,指令字的大小一般是固定的。扩展编码按照操作码从短到长进行编码。算了先放一下。我先看一下别的复习资料。等会儿再看这个题。 鼓励自己 …...
GCD 深入解析:从使用到底层实现
前言 Grand Central Dispatch (GCD) 是 Apple 基于 C 语言开发的一套完整的并发编程框架。它不仅仅是一个简单的线程管理工具,而是一个高度优化的并发编程解决方案。GCD 的设计理念是将并发编程的复杂性封装在框架内部,为开发者提供简单易用的接口。本文…...
JavaScript中的AES加密与解密:原理、代码与实战
前言 关于有js加密、js解密,js业务相关,找jsjiami官网站长v。 另外前段时间做了个单子跑单了,出售TEMU助手。eller点kuajingmaihuo点com的全自动化助手,可以批量合规,批量实拍图,批量资质上传等。 一、A…...
计算机组成原理实验(7) 堆指令部件模块实验
实验七 堆指令部件模块实验 一、实验目的 1、掌握指令部件的组成方式。 2、熟悉指令寄存器的打入操作,PC计数器的设置和加1操作,理解跳转指令的实现过程。 二、实验要求 按照实验步骤完成实验项目,掌握数据打入指令寄存器IR1、PC计数器的…...
Windows系统下Node.js环境部署指南:使用nvm管理多版本
Windows系统下Node.js环境部署指南:使用nvm管理多版本 一、Node.js介绍二、为什么需要nvm?三、安装前的准备工作1. 本次环境说明2. 卸载现有Node.js(如有) 三、nvm-windows安装步骤1. 下载安装包2. 安装过程3. 验证安装 四、使用n…...
数据结构*队列
队列 什么是队列 是一种线性的数据结构,和栈不同,队列遵循“先进先出”的原则。如下图所示: 在集合框架中我们可以看到LinkedList类继承了Queue类(队列)。 普通队列(Queue) Queue中的方法 …...
C语言蓝桥杯真题代码
以下是不同届蓝桥杯C语言真题代码示例,供参考: 第十三届蓝桥杯省赛 C语言大学B组 真题:卡片 题目:小蓝有很多数字卡片,每张卡片上都是数字1-9。他想拼出1到n的数列,每张卡片只能用一次,求最大的…...
Sharding-JDBC分库分表中的热点数据分布不均匀问题及解决方案
引言 在现代分布式应用中,使用Sharding-JDBC进行数据库的分库分表是提高系统性能和扩展性的常见策略。然而,在实际应用中,某些特定的数据(如最新订单、热门商品等)可能会成为“热点”,导致这些部分的数据处…...
Dagster中的Ops与Assets:数据管道构建的两种选择
Dagster是一个强大的数据编排平台,它提供了多种工具来帮助数据工程师构建可靠的数据管道。在Dagster中,Ops和Assets是两种核心概念,用于定义数据处理逻辑。本文将全面介绍Ops的概念、特性及其使用方法,特别补充了Op上下文和Op工厂…...
thonny提示自动补全功能
THONNY IDE 自动补全功能配置 在 Thonny IDE 中启用和优化自动补全功能可以显著提升编程体验。为了确保该功能正常工作,需要确认几个设置选项。 配置自动补全 Thonyy IDE 的自动补全默认情况下是开启的。如果发现自动补全未按预期运行,可以通过调整首选…...
PyTorch_阿达玛积
阿达玛积指的是矩阵对应位置的元素相乘,可以使用乘号运算符,也可以使用mul函数来完成计算。 代码 import torch import numpy as np # 1. 使用 mul 函数 def test01():data1 torch.tensor([[1, 2], [3, 4]])data2 torch.tensor([[5, 6], [7, 8]])dat…...
蓝桥杯 摆动序列
摆动序列 原题目链接 题目描述 如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。 即对于任意整数 i(i ≥ 1)满足: a₂ᵢ < a₂ᵢ₋₁,a₂ᵢ₊₁ > a₂ᵢ 小明想知道&…...
AI 与生物技术的融合:开启精准医疗的新纪元
在科技飞速发展的今天,人工智能(AI)与生物技术的融合正在成为推动医疗领域变革的重要力量。精准医疗作为现代医学的重要发展方向,旨在通过深入了解个体的基因信息、生理特征和生活方式,为患者提供个性化的治疗方案。AI…...
三、shell脚本--运算符与表达式:让脚本学会“思考”
一、算术运算符:加减乘除取模 在我们写shell脚本时,做点基本的数学运算还是经常需要的。常用的算术运算符跟我们平时学的一样: : 加- : 减* : 乘 (小提示:有时候在某些命令里可能需要写成 \*)/ : 除 (在 Shell 里通常是取整数部分…...
c++ 指针参数传递的深层原理
指针参数传递的深层原理 理解为什么可以修改指针指向的内容但不能直接修改指针本身,需要深入理解指针在内存中的表示方式和函数参数传递机制。 1. 指针的内存表示 指针本质上是一个变量,它存储的是另一个变量的内存地址。在内存中: 假设有…...
【查看.ipynp 文件】
目录 如何打开 .ipynb 文件? 如果确实是 .ipynp 文件: .ipynp 并不是常见的 Jupyter Notebook 文件格式。通常,Jupyter Notebook 文件的扩展名是 .ipynb(即 Interactive Python Notebook)。如果你遇到的是 .ipynb 文…...
C++ 简单工厂模式详解
简单工厂模式(Simple Factory Pattern)是最简单的工厂模式,它不属于GoF 23种设计模式,但它是工厂方法模式和抽象工厂模式的基础。 概念解析 简单工厂模式的核心思想是: 将对象的创建逻辑集中在一个工厂类中 客户端不…...
ubuntu使用apt安装软件
1、使用apt list |grep jdk查看要安装的软件 此处以jdk为例 2、执行名称:安装指定版本的软件 sudo apt install openjdk-11-jdk...
TFT(薄膜晶体管)和LCD(液晶显示器)区别
TFT(薄膜晶体管)和LCD(液晶显示器)是显示技术中常见的术语,二者既有联系又有区别。以下是它们的核心区别和关系: 1. 基本概念 LCD(液晶显示器) LCD是一种利用液晶材料特性控制光线通…...
【文献阅读】中国湿地随着保护和修复的反弹
一、研究背景 滨海湿地是全球最具生态价值的生态系统之一,广泛分布在河口、潮间带、泻湖和盐沼等地带,在调节气候、水质净化、生物栖息以及防止海岸侵蚀等方面发挥着关键作用。然而,近年来滨海湿地正面临严峻威胁,全球估计约有50%…...
用Ensaio下载GIS数据
文章目录 简介重力场绘制 简介 Ensaio在葡萄牙语中是随笔的意思,是一个用于下载开源数据集的python库。其底层基于Pooch来下载和管理数据。 Ensaio可通过pip或者conda来安装 pip isntall ensaio conda install ensaio --channel conda-forge由于这个库功能较为单…...
【算法基础】递归算法 - JAVA
一、递归基础 1.1 什么是递归算法 递归算法是一种通过函数调用自身来解决问题的方法。简单来说,就是"自己调用自己"。递归将复杂问题分解为同类的更简单子问题,直到达到易于直接解决的基本情况。 1.2 递归的核心要素 递归算法由两个关键部…...
连续变量与离散变量的互信息法
1. 互信息法简介 互信息(Mutual Information, MI) 是一种衡量两个变量之间相互依赖程度的统计量,它来源于信息论。互信息可以用于评估特征与目标变量之间的相关性,无论这些变量是连续的还是离散的。互信息法是一种强大的特征选择…...
java_Lambda表达式
1、背景 lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。lambda表达式就和方法一样样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式和一个代码块)。La…...
Python Cookbook-6.17 NuIl对象设计模式的实现
任务 你想减少代码中的条件声明,尤其是针对特殊情况的检查。 解决方案 一种常见的代表“这里什么也没有”的占位符是 None,但我们还可以定义一个类,其行为方式和这种占位符相似,而且效果更好: class Null(object):Null对象总是…...
Java接口全面教程:从入门到精通
目录 接口的基本概念 接口的特性 1. 访问修饰符 2. 接口中的常量 3. 接口中的方法 3.1 抽象方法(传统用法) 3.2 默认方法(Java 8 引入) 3.3 静态方法(Java 8 引入) 3.4 私有方法(Java …...
Power Query精通指南3:数据库(查询折叠与数据隐私)、批量合并文件、自定义函数
文章目录 九、批量合并文件9.1 案例背景9.2 合并文件的标准流程9.3 示例:合并文件9.3.1 连接到文件夹9.3.1.1 连接到本地 / 网络文件夹9.3.1.2 连接到 SharePoint 文件夹9.3.1.3 连接到 OneDrive for Business9.3.1.4 连接到其他文件系统 9.3.2 筛选文件9.3.3 合并文…...
Python 学习
这里主要是为了记录我学习Python的过程,更多是使我规范书写Pyhton语言! 1. 第一章 Python 定义:一种解释型的语言,区别于其他的高级语言,逐行翻译进行执行。 过程:首先编写编程语言,利用Pytho…...
生成式 AI 的优势
在科技飞速发展的今天,人工智能已经不再是一个遥不可及的概念,而是逐渐渗透到我们生活的方方面面。其中,生成式 AI 更是如同一颗璀璨的新星,在人工智能的浩瀚星空中闪耀着独特的光芒。它究竟有哪些令人瞩目的优势,又为何会成为我们这个时代无法忽视的存在呢? 生成式 AI …...
Hal库下备份寄存器
首先要确保有外部电源给VBAT供电 生成后应该会有这两个文件(不知道为什么生成了好几次都没有,复制工程在试一次就有了) 可以看到stm32f407有20个备份寄存器 读写函数 void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t Backup…...
P1537 数字反转(升级版)详解
这个题目还是对于新手比较锻炼思维严谨性的,我认为是在我做过的一些题目中,此题算上等马 先看题目 我先说明我自己的思路,以及这个题目你需要特别注意的地方 1,数字反转,①可用<algorithm>库里面的reverse函数…...
operator 可以根据需要重载 == 运算符进行比较
要将 vector<AppInfo> 类型的 A 和 B 两个容器进行比较,并且当 B 中有 A 中没有的元素时,插入到数据库中,你可以通过以下步骤实现: 比较元素:遍历 vector<B>,检查每个元素是否在 vector<A&…...
网格不迷路:用 CSS 网格生成器打造完美布局
前言 你是否曾因写错 grid-template-areas 而捶键盘?是否在面对千层嵌套的复杂布局时,瞬间怀疑人生,甚至思考要不要转行去卖奶茶?别慌,CSS 网格生成器闪亮登场,像拼乐高一样,帮你轻松搭建网页结构,还能自动输出干净代码,堪称“前端界的乐高大师”。让我们放下枯燥的代…...