【多线程】定时器

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 定时器是什么?
  • 2. 定时器的应用场景
  • 3. Timer类的使用
    • 3.1 Timer类创建定时器
    • 3.2 schedule()方法的介绍
    • 3.3 使用Timer管理多个任务
  • 4. 如何自己实现一个定时器?
    • 4.1 需考虑的问题
      • 4.1.1 如何实现自定义 MyTimer 类同时管理多个任务
      • 4.1.2 如何保证多线程下操作 PriorityQueue 线程安全
    • 4.2 实现的思路
      • 4.2.1 自定义类 MyTimer
      • 4.2.2 构造线程执行任务
    • 4.3 存在的问题
      • 4.3.1 当前队列里的 Mytask 元素是按照什么规则表示优先级的?
      • 4.3.2 while(true)带来CPU忙等问题
    • 4.4 最终完整代码

1. 定时器是什么?

定时器】就是闹钟,就是设定一个时间,当该时间一到,可执行一个指定的代码,即在预设的时间执行一个或多个动作

Java 标准库(java.util)中提供定时器类:Timer类,其核心方法:schedule()

2. 定时器的应用场景

定时器的应用场景非常多!

尤其在网络编程中,在实际中,很多的"等待"不应该是无止境地等待,应该有一个期限!比如打开浏览器访问某个网站,如果正好此时的网络信号不佳,则可能加载很长时间,浏览器会设置一个超时时间,如果访问该页面等待时间超过这个超时时间仍然没有结果,则提醒用户无需等待下去(浏览器会提示 504 gateway time out)

比如数据备份,服务器每天凌晨自动备份数据,使用定时器每天在指定时间执行数据备份的操作;再比如定时邮件发送,自动发送定时的提醒邮件等,设置定时器在特定时间执行发送邮件的操作,还有缓存更新,缓存数据定期更新,以保持数据的时效性,定时器周期性地更新缓存等等

总之,定时器在 Java 应用中十分常见,尤其是在需要按计划执行操作的时候

3. Timer类的使用

3.1 Timer类创建定时器

Timer 类提供以下 4 个构造方法:

public Timer() 
public Timer(boolean isDaemon)
public Timer(String name)
public Timer(String name, boolean isDaemon)

其中参数 name 是线程的名字,isDaemon参数设置为 true 表示将该线程设置为后台线程,默认是前台线程

(关于前后台线程可以回顾前期内容 Thread 类及其基本用法,其中属性方法是否为后台线程 --> isDaemon()中有介绍)

以下是使用 Timer 类创建定时器,具体代码如下:

public class ThreadDemo {public static void main(String[] args) {//1.创建 timer 对象Timer timer = new Timer();//2. 设定时间timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello1!");}},2000);System.out.println("hello2!");}
}

打印结果:

在这里插入图片描述
运行结果可以看到,实现了该效果,先立即执行打印 hello2,等待 2s 后,执行定时器中的操作,打印 hello1

注意】这里我们可以看到打印 hello1 后,程序并未终止退出,这是为什么呢?
这里是因为 Timer 类里内置的前台线程前台线程会阻止当前进程结束!实际上,run()方法的执行正是依赖 Timer 类中内部线程控制时间到了之后再执行!!!

3.2 schedule()方法的介绍

在 Java 的 Timer 类中,schedule() 方法用于安排一个 TimerTask 任务在未来的某个时间点执行,Timer 类有几个不同的 schedule() 方法,它们之间的主要区别在于任务的执行是一次性的还是周期性的,带有参数 period 的方法则是可以周期性执行!

void schedule(TimerTask task, long delay)
void schedule(TimerTask task, Date time)
void schedule(TimerTask task, Date firstTime,long period)
void schedule(TimerTask task, long delay,long period)

以上 4 个构成重载,第一个参数均为被安排的任务,后面的参数表示设定任务将要等待的时间,具体解释说明这两个参数:task 和 delay
task】task to be scheduled,被安排的任务(安排一个工作,说明这个工作不是立刻完成,而是在未来的某个时间点完成)
delay】delay in milliseconds before task is to be executed,任务执行前的延迟时间,以毫秒为单位

public class ThreadDemo {public static void main(String[] args) {//1.创建 timer 对象Timer timer = new Timer();//2. 设定时间timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello1!");}},4000);}
}

上述代码 schedule() 方法的第一个参数是 TimerTask,本质上就是 Runnable,通过查看 TimerTask 源代码可以看出, TimerTask 就是一个实现了 Runnable 接口的抽象类!因此,当我们创建 TimerTask 对象的时候,需要重写 run() 方法,run() 方法中写的内容定义的行为,即表示任务具体要做什么的内容

图解说明:
在这里插入图片描述

3.3 使用Timer管理多个任务

定时器内部管理的不仅仅是一个任务,可以管理很多很多任务!

那么我们会想这样一个问题,如果有很多个任务,比如几万个,假如定时器里面有几万个任务,如果创建几万个线程,会消耗非常多的资源,这是一个多么庞大的数字!!!

虽然任务可能会有很多个,它们的触发的时间是不同的,因此,只需要一个或者一组工作线程,每次找到这些任务中最先到达时间的任务,即在一个线程中,先执行最早的任务,根据触发时间依次执行,时间到了则执行,没有到则等待,这样就不需要创建那么多线程也可以按照指定时间依次执行任务了!

以下代码给定时器同时定义 5 个任务:

public class Test {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello1");}},1000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello3");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello4");}},4000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello5");}},5000);}
}

效果如下:
在这里插入图片描述
运行程序后,根据时间,先打印 hello1,接着每间隔 1s 打印,实现该效果!

4. 如何自己实现一个定时器?

4.1 需考虑的问题

4.1.1 如何实现自定义 MyTimer 类同时管理多个任务

从上述使用 Timer 管理多个任务可以看到,该机制是需要一个/一组工作线程,每次找到这些任务中最先到达时间的任务,在一个线程中,先执行最早的任务,根据触发时间依次执行,时间到了则执行,没有到则等待

我们自己实现 MyTimer 类,如何才能实现这样的机制呢?

!!! 堆是这里需要用到的核心数据结构,在 Java 标准库中直接就提供了优先级队列 —— PriorityQueue
因此,我们使用 PriorityQueue 来实现

可能会有疑问,为什么不用排序?
要知道,排序的效率要低于堆,并且在插入新元素的时候,想要维护原有序列的规律是比较困难的
(有直接的优先级队列,为什么不用捏!)

4.1.2 如何保证多线程下操作 PriorityQueue 线程安全

定时器可能有多个线程执行 schedule() 方法,那么希望在多线程下操作 PriorityQueue 还能够保证线程安全!

要保证线程安全,进一步我们思考,Java 标准库中提供带优先级的阻塞队列 —— PriorityBlockingQueue ,能够解决这个线程安全问题

上期内容多线程系列中介绍了阻塞队列,BlockingQueue 接口有 7 个实现类,其中有一个类是 PriorityBlockingQueue

PriorityBlockingQueue】是支持优先级排序的无界阻塞队列,它遵循优先级队列规则,即可以实现根据任务的执行时间来建立小根堆,取头元素则是当前该队列中的最小元素,并且带优先级的阻塞队列同样只有入队列和出队列有阻塞特性!其它方法则不具备阻塞特性

4.2 实现的思路

4.2.1 自定义类 MyTimer

首先创建一个自定义类 MyTimer 类来模拟 Timer 类,创建这个类要表示两方面的信息:

1)执行的任务是什么 (Mytask用于描述一个要执行的任务)
2)任务执行的时间(为后续判定方便,这里使用绝对的时间戳)

绝对时间戳
MyTimer 类中定义的schedule()方法中,其中需要传入一个参数 delay,表示的是相对时间,如 5000 ms,表示在 5s 后执行该任务,因此,在构造 MyTask 的时候,需要将相对时间转换为绝对时间,如下:

this.time = System.currentTimeMillis()+delay;

其中 System.currentTimeMillis() 用于获取当前毫秒级别的时间戳,即为当前时刻和基准时刻 ms 数之差
(基准时刻是 1970 年 1 月 1 日 00:00:00)
在这里插入图片描述
MyTimer 类封装核心数据结构 —— PriorityBlockingQueue,MyTask 作为元素放在这个优先级阻塞队列中

代码如下:

//表示一个任务
class MyTask {public Runnable runnable;//为了方便后续判断使用绝对的时间戳public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;//取当前时刻的时间戳+delay作为该任务实际执行的时间戳this.time = System.currentTimeMillis()+delay;}
}class MyTimer {//这个结构就是带有优先级的阻塞队列 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();//此处delay是一个形如5000这样的数字(多长时间之后执行该任务)//这列的元素需要手动封装//创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行public void schedule(Runnable runnable, long delay) {//根据参数构造MyTask,插入队列即可MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);}
}

4.2.2 构造线程执行任务

定时器类中的构造方法中构造一个线程负责执行具体任务,判断带有优先级的阻塞队列中的各个任务是否到达可执行的时间

先从 queue 中取出一个元素任务,这个任务的时间是所有任务执行实现最早的,再获取当前的时间,通过比较当前时间和该任务的时间,判断是否达到了该任务的执行时间,如果达到了,则执行 run()方法,未达到,则将取出的任务放回队列中

为什么要使用绝对时间?
因为我们要将执行时间与此刻的时间戳进行对比,判断是否执行任务

代码如下:

class MyTimer {//这个结构就是带有优先级的阻塞队列 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();//此处delay是一个形如5000这样的数字(多长时间之后执行该任务)//这列的元素需要手动封装//创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行public void schedule(Runnable runnable, long delay) {//根据参数构造MyTask,插入队列即可MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);}//在这里构造一个线程负责执行具体任务public MyTimer() {Thread t = new Thread(() -> {while(true) {try {synchronized (locker) {//阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素MyTask myTask = queue.take();//当前队列无元素 队列阻塞 退出循环//队列有元素就可以获取到元素//看钱当前任务时间是否合适long curTime = System.currentTimeMillis();if (myTask.time <= curTime) {//时间到了可以执行任务了myTask.runnable.run();} else {//时间没到//把刚才取出的任务重新塞回队列中queue.put(myTask);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}

4.3 存在的问题

但是上述代码存在两个严重的bug!!!

4.3.1 当前队列里的 Mytask 元素是按照什么规则表示优先级的?

如果 Mytask 元素没有实现比较方法规则,在添加元素是可能会抛出 classCastException 异常,因为优先级队列无法确定对象之间排序的顺序!(注意这里我们比较的是对象,而不是简单的数字等)

忘记对象比较的小伙伴们可以看看这期内容:对象的比较

因此 PriorityBlockingQueue 可以通过直接实现 Comparable 接口并重写该接口的 compareTo 方法,也可以通过 自定义比较器类,实现Comparator接口

这里使用 Mytask 类实现Comparable 接口,并重写 compareTo 方法,通过对象的时间 time 来进行比较

代码如下:

class MyTask implements Comparable<MyTask>{public Runnable runnable;//为了方便后续判断使用绝对的时间戳public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;//取当前时刻的时间戳+delay作为该任务实际执行的时间戳this.time = System.currentTimeMillis()+delay;}@Overridepublic int compareTo(MyTask o) {//取时间最小的元素return (int)(this.time-o.time);}
}

4.3.2 while(true)带来CPU忙等问题

忙等,CPU确实是在等着,但是没有休息,等待过程中占用着CPU,就比如我点的餐,显示8:30做好,我期待着它能提前做好,8:01去看一下,8:0去看一下餐好没好,又过5分钟,我又去看一下,剩下的时间我一直不停地看时间,去看餐到底好没好,这段时间算是浪费了!

忙等在上述代码中表现为while(true)的执行内容中,每秒钟可能访问队首元素非常多次,但是还没有到时间,离执行时间还有很久,这是在做无意义的事情,造成了CPU的浪费!

解决的方式就是需要在等待过程中释放 CPU,提到忙等,我就立马想到了使用 wait() 方法,不知道各位想起小丁忙等的故事了嘛~ wait 方便随时提前唤醒,比如当前时刻是14:30,约定14:40需要执行上课这个任务,取出队首元素,发现时间没到,则 wait 等待10min,在等待过程中,突然来一个任务,比如14:35要去接水,则就不能一直等待了,而是唤醒notify,此时工作线程就会重新取队首元素,即14:35的接水任务!

因此,时间还没有到,则将刚取出来的队首元素放回队列,并进入 wait 等待,一直到时间到,将它唤醒,如果插入新元素,调用 notify,唤醒锁对象

代码如下:

//表示一个任务
class MyTask implements Comparable<MyTask>{public Runnable runnable;//为了方便后续判断使用绝对的时间戳public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;//取当前时刻的时间戳+delay作为该任务实际执行的时间戳this.time = System.currentTimeMillis()+delay;}@Overridepublic int compareTo(MyTask o) {return (int)(this.time-o.time);//取时间最小的元素}
}
class MyTimer {//创建一个锁对象private Object locker = new Object();//这个结构就是带有优先级的阻塞队列 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();//此处delay是一个形如3000这样的数字(多长时间之后执行该任务)//这列的元素需要手动封装//创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行public void schedule(Runnable runnable, long delay) {//根据参数构造MyTask,插入队列即可MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);synchronized (locker) {locker.notify();}}//在这里构造一个线程负责执行具体任务public MyTimer() {Thread t = new Thread(() -> {while(true) {try {synchronized (locker) {//阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素MyTask myTask = queue.take();//当前队列无元素 队列阻塞 退出循环//队列有元素就可以获取到元素//看钱当前任务时间是否合适long curTime = System.currentTimeMillis();if (myTask.time <= curTime) {//时间到了可以执行任务了myTask.runnable.run();} else {//时间没到//把刚才取出的任务重新塞回队列中queue.put(myTask);//实时时间进行调整locker.wait(myTask.time-curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}

深入解析:
1)为什么是在 schedule() 方法里 notify()
因为在 schedule() 方法中使用 notify 是为了确保正在等待的线程能够及时得到通知,重新检查队列并执行更早的任务,因为可能新来的任务,执行时间更早!

2)确定加锁位置问题

我们来分析一下,以下两种加锁位置:

在这里插入图片描述
第一种加锁位置即为上述代码写的,是正确的,第二种加锁位置是错误的,将会引起线程安全问题,会出现空打一炮的情况,使新的任务执行时间更早而无法及时执行

对此,我们进一步分析第二种加锁位置:假设线程 t1 执行到 put 时候,切到线程 t2 执行
在这里插入图片描述
接下来,等 t1 线程继续执行的时候,将要 wait 等待30min,线程 t2 的 notify 已经执行过了,wait 已经错过 notify了,此时的 wait 就会导致新的任务无法及时执行!!!所以是有问题的~

那第一种加锁位置,为什么就正确呢?仍然假设线程 t1 执行到 put 时候,切到线程 t2 执行
在这里插入图片描述
因此,新加一个12:10的任务,在 t1 线程执行到 wait 前,t2 线程因为没有锁进行阻塞等待,当 t1 进入 wait,t1 释放锁,t2 插入新的任务竞争到锁,并执行 notify 唤醒 t1 线程,让 t1 线程重新扫描阻塞队列中的任务,发现比原来更早的执行任务12:10,进行更新执行这个更早的任务!

总之,多线程是很复杂的,稍不留神就很容易出现错误!牢记线程安全问题的根本原因:抢占式执行!!!

4.4 最终完整代码

//表示一个任务
class MyTask implements Comparable<MyTask>{public Runnable runnable;//为了方便后续判断使用绝对的时间戳public long time;public MyTask(Runnable runnable, long delay) {this.runnable = runnable;//取当前时刻的时间戳+delay作为该任务实际执行的时间戳this.time = System.currentTimeMillis()+delay;}@Overridepublic int compareTo(MyTask o) {return (int)(this.time-o.time);//取时间最小的元素}
}
class MyTimer {//创建一个锁对象private Object locker = new Object();//这个结构就是带有优先级的阻塞队列 核心数据结构private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();//此处delay是一个形如3000这样的数字(多长时间之后执行该任务)//这列的元素需要手动封装//创建一个类 表示两方面 1.执行的任务是啥 2.任务啥时候执行public void schedule(Runnable runnable, long delay) {//根据参数构造MyTask,插入队列即可MyTask myTask = new MyTask(runnable,delay);queue.put(myTask);synchronized (locker) {locker.notify();}}//在这里构造一个线程负责执行具体任务public MyTimer() {Thread t = new Thread(() -> {while(true) {try {synchronized (locker) {//阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素MyTask myTask = queue.take();//当前队列无元素 队列阻塞 退出循环//队列有元素就可以获取到元素//看钱当前任务时间是否合适long curTime = System.currentTimeMillis();if (myTask.time <= curTime) {//时间到了可以执行任务了myTask.runnable.run();} else {//时间没到//把刚才取出的任务重新塞回队列中queue.put(myTask);//实时时间进行调整locker.wait(myTask.time-curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}
public class ThreadDemo {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hi4");}},4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hi3");}},3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hi2");}},2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hi1");}},1000);System.out.println("hi0");}
}

效果如下:
在这里插入图片描述
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述✨✨✨本期内容到此结束啦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/50885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工作流 Flowable

工作流包括业务流和审批流等业务流程。 在一个流程系统中&#xff0c;任务间往往存在复杂的依赖关系&#xff0c;为保证pipeline的正确执行&#xff0c;就是要解决各任务间依赖的问题&#xff0c;这样DAG结合拓扑排序是解决存在依赖关系的一类问题的利器。DAG ( Directed Acyc…

池化层pytorch最大池化练习

神经网络构建 class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 MaxPool2d(kernel_size3, ceil_modeFalse)def forward(self, input):output self.maxpool1(input)return output Tensorboard 处理 writer SummaryWriter("./l…

【React】详解如何获取 DOM 元素

文章目录 一、基础概念1. 什么是DOM&#xff1f;2. 为什么需要获取DOM&#xff1f; 二、使用 ref 获取DOM元素1. 基本概念2. 类组件中的 ref3. 函数组件中的 ref 三、 ref 的进阶用法1. 动态设置 ref2. ref 与函数组件的结合 四、处理特殊情况1. 多个 ref 的处理2. ref 与条件渲…

基于STM32F103的FreeRTOS系列(四)·FreeRTOS资料获取以及简介

目录 1. FreeRTOS简介 1.1 FreeRTOS介绍 1.2 为何选择FreeRTOS 1.3 FreeRTOS资料获取 1.3.1 官网下载 1.3.2 Github下载 1.3.3 托管网站下载 1.4 FreeRTOS的编程风格 1.4.1 数据类型 1.4.2 变量名 1.4.3 函数名 1.4.4 宏 1. FreeRTOS简介 1.1 Free…

11. Hibernate 持久化对象的各种状态

1. 前言 本节课和大家聊聊持久化对象的 3 种状态。通过本节课程&#xff0c;你将了解到&#xff1a; 持久化对象的 3 种状态&#xff1b;什么是对象持久化能力。 2. 持久化对象的状态 程序运行期间的数据都是存储在内存中。内存具有临时性。程序结束、计算机挂机…… 内存中…

Web前端浅谈ArkTS组件开发

本文由JS老狗原创。 有幸参与本厂APP的鸿蒙化改造&#xff0c;学习了ArkTS以及IDE的相关知识&#xff0c;并有机会在ISSUE上与鸿蒙各路大佬交流&#xff0c;获益颇丰。 本篇文章将从一个Web前端的视角出发&#xff0c;浅谈ArkTS组件开发的基础问题&#xff0c;比如属性传递、插…

hamcrest 断言框架使用示例和优势分析

引言 在软件测试领域&#xff0c;断言是验证代码行为是否符合预期的关键环节。Hamcrest 断言框架&#xff0c;以其独特的匹配器&#xff08;Matcher&#xff09;概念和清晰的失败信息&#xff0c;赢得了广泛的赞誉。尽管 Python 标准库中没有内置的 Hamcrest 库&#xff0c;但…

【Linux】-----工具篇(编译器gcc/g++,调试器gdb)

目录 一、gcc/g 简单认识 程序的翻译过程认识gcc 预处理(宏替换) 编译 汇编 链接 宏观认识 如何理解&#xff08;核心&#xff09; 什么是链接&#xff1f; 链接的分类 二、gdb 基本的认识 基本操作及指令 安装gdb 启动gdb ​编辑 显示源代码(list) 运行程序…

SQL labs-SQL注入(三,sqlmap使用)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 引言&#xff1a; 盲注简述&#xff1a;是在没有回显得情况下采用的注入方式&#xff0c;分为布尔盲注和时间盲注。 布尔盲注&#xff1a;布尔仅有两种形式&#xff0c;ture&#…

学习笔记:MySQL数据库操作3

1. 创建数据库和表 创建数据库 mydb11_stu 并使用该数据库。创建 student 表&#xff0c;包含字段&#xff1a;学号&#xff08;主键&#xff0c;唯一&#xff09;&#xff0c;姓名&#xff0c;性别&#xff0c;出生年份&#xff0c;系别&#xff0c;地址。创建 score 表&…

C#、Net6、WebApi报表方案

目录 1 Pdf表单方案 1.1出现如下错误提示: 1.2 字体路径使用 2 Docx报表模板方案 2.1 pdf方案缺陷 2.2 解决方案 3 Spire.Doc报表方案 3.1 Docx方案缺陷 3.2 解决方案 4 插入复选框 5 WebApi文件流下载接口 6 软件获取方式 1 Pdf表单方案 使用【Adobe Acrobat P…

python—pandas基础(2)

文章目录 列操作修改变量列筛选变量列使用.loc[]&#xff08;基于标签)使用.iloc[]&#xff08;基于整数位置&#xff09;使用.filter()方法 删除变量列添加变量列 变量类型的转换Pandas 支持的数据类型在不同数据类型间转换 建立索引新建数据框时建立索引读入数据时建立索引指…

Vue---vue3+vite项目内使用devtools,快速开发!

背景 我们在前期开发时&#xff0c;一般使用chrome或者edge浏览器&#xff0c;会使用vue-devtools或react-devtools&#xff08;此插件个人未使用&#xff0c;可百度下是否可内嵌入项目&#xff01;&#xff09;来审查vue项目&#xff1b;这个需要安转浏览器插件才可支持&…

使用PyTorch导出JIT模型:C++ API与libtorch实战

PyTorch导出JIT模型并用C API libtorch调用 本文将介绍如何将一个 PyTorch 模型导出为 JIT 模型并用 PyTorch 的 CAPI libtorch运行这个模型。 Step1&#xff1a;导出模型 首先我们进行第一步&#xff0c;用 Python API 来导出模型&#xff0c;由于本文的重点是在后面的部署…

【odoo17】后端py方法触发右上角提示组件

概要 在前面文章中&#xff0c;有介绍过前端触发的通知服务。 【odoo】右上角的提示&#xff08;通知服务&#xff09; 此文章则介绍后端触发方法。 内容 直接上代码&#xff1a;但是前提一定是按钮触发&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; def bu…

【css】实现扫光特效

对于要重点突出的元素&#xff0c;我们经常可以看到它上面打了一个从左到右的斜向扫光&#xff0c;显得元素亮闪闪的&#xff01;类似于下图的亮光动效 关键步骤 伪元素设置position :absolute【也可以不用伪元素&#xff0c;直接创建一个absolute元素盖在上面】设置渐变line…

Mike21粒子追踪模型particle tracking如何展示粒子轨迹

前言&#xff1a; 随着模型的推广&#xff0c;模型的很多模块也问的多了起来&#xff0c;PT粒子追踪模块最近群友也在问&#xff0c;结果算了出来&#xff0c;却实现不了展示运动轨迹。今天就写段简单的PT后处理的方法吧。 注意&#xff1a;MIKE21输出模块中不但输出了关于水…

Axure怎么样?全面功能评测与用户体验分析!

软件 Axure 曾经成为产品经理必备的原型设计工具&#xff0c;被认为是专门为产品经理设计的工具。但事实上&#xff0c;软件 Axure 的使用场景并不局限于产品经理构建产品原型。UI/UX 设计师还可以使用 Axure 软件构件应用程序 APP 原型&#xff0c;网站设计师也可以使用 Axure…

如何系统的学习C++和自动驾驶算法

给大家分享一下我的学习C和自动驾驶算法视频&#xff0c;收藏订阅都很高。打开下面的链接&#xff0c;就可以看到所有的合集了&#xff0c;订阅一下&#xff0c;下次就能找到了。 【C面试100问】第七十四问&#xff1a;STL中既然有了vector为什么还需要array STL中既然有了vec…

QSqlQuery增删改查

本文记录使用QSqlQuery实现增删改查的过程。 目录 1. 构建表格数据 声明变量 表格、数据模型、选择模型三板斧设置 列表执行查询 列表的水平表头设置 2. 新增一行 构建一个空行 通过dialog返回的修改行数据&#xff0c;update更新 3. 更新一行 获取到需要更新的行 通…