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

2023.10.22 关于 定时器(Timer) 详解

目录

引言

标准库定时器使用

自己实现定时器的代码

模拟实现的两大方面

核心思路

重点理解

 自己实现的定时器代码最终代码版本


引言

  • 定时器用于在 预定的时间间隔之后 执行特定的任务或操作

实例理解:

  • 在服务器开发中,客户端向服务器发送请求,等待服务器响应,但可能因为某一故障,导致程序一直无法响应,从而容易出现客户端卡死的情况,所以为了应对该情况,我们通常可以设置一个定时器,若未在规定的时间内完成任务,则可以做一些操作,来取消客户端的等待

标准库定时器使用

import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo24 {public static void main(String[] args) {System.out.println("程序启动");
//        这个 Timer 类就是标准库的定时器Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务A");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务B");}},4000);}
}
  • 上述代码中的 schedule 方法的效果是给定时器 注册一个任务,任务不会立即执行,而是在指定时间进行执行
  • 上述代码中的 schedule 方法有两个参数,一个参数为 TimerTask 接口,对 run 方法进行重写,从而指定要执行的任务,另一个参数为等待时间且单位为毫秒

注意:

  • 一个定时器可以同时安排多个任务
  • 定时器执行完任务之后,进程并不会立即退出,因为定时器内部需要维护一组线程来执行这些任务,这些线程被称为 前台线程
  • 当我创建一个定时器并安排任务时,定时器会启动一个或多个线程,这些线程负责按计划执行任务
  • 这些线程会一直运行,直到定时器被取消或程序显式地终止,这样做到目的是为了确保定时器能够准时执行任务,即使主线程已经完成或已退出
  • 由于这些前台线程在定时器内部运行,所以它们会影响进程的退出
  • 如果定时器中的任务尚未完成,这些前台线程将阻止进程退出,直到所有任务执行完毕或定时器被取消
  • 这确保了任务得到完整执行,并且程序能够正常结束
  • 从而需要注意的是,在使用定时器时不再需要它,我们应该主动取消定时器以释放资源并停止前台线程的执行
  • 这样可以避免不必要的资源占用和线程执行

自己实现定时器的代码

模拟实现的两大方面

  • 在指定时间执行所注册的任务
  • 一个定时器可注册多个任务,且这多个任务按照约定时间,顺序执行

核心思路

  • 有一个扫描线程,负责判定任务是否到执行时间
  • 需要一个 数据结构 来保存所有被注册的任务

注意:

  • 此处的每个任务均带有时间,并且一定是时间越靠前,就执行
  • 所以在当下的场景中使用 优先级队列 便是一个很好的选择
  • 时间小的,作为优先级高的
  • 此时队首元素 就是整个队列中 最先要执行的任务
  • 此时 扫描线程仅需扫描一下队首元素即可,不必遍历整个队列
  • 因为队首元素还没到执行时间,后续元素更不可能到执行时间
  • 当然 此处的优先级队列会在 多线程 环境下使用
  • 因为 调用 schedule 方法是一个线程,扫描是另一个线程,从而此处涉及到线程安全问题
  • 我们可以使用 标准库提供的 PriorityBlockingQueue ,阻塞队列本身就是线程安全的,所以 带优先级的阻塞队列 便十分切合我们的需求

  • 以下是一个自己实现的定时器
import java.util.concurrent.PriorityBlockingQueue;class MyTask implements Comparable<MyTask>{
//    要执行的任务内容private Runnable runnable;
//    任务在啥时候执行(使用毫秒时间戳表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}
//    获取当前任务的时间public long getTime() {return time;}
//    执行任务时间public void  run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {
//        当前想要实现队首元素是 时间最小的任务
//        这两是 谁减谁,不需要去记,往往可以试一试就知道了
//        要么就是 this.time - o.time, 要么就是 o.time - this.timereturn (int) (this.time - o.time);}
}class MyTimer {
//    扫描线程private Thread t = null;public MyTimer() {t = new Thread(() -> {while (true) {
//               取出队首元素,检查看看队首元素任务是否到时间了try {synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {
//                         如果时间还没到,就把任务塞回队列queue.put(myTask);
//                           在 put 之后,进行一个 wait 等待this.wait(myTask.getTime() - curTime);}else {
//                           如果时间到了,就把任务进行执行myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}//    有一个阻塞优先级队列,来保存任务private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//    指定两个参数
//    第一个参数是 任务 内容
//    第二个参数是 任务 在多少毫秒之后执行 如 1000public void schedule(Runnable runnable,long after) {
//        注意这里的时间换算,获取当前时间的时间戳加上需要等待的时间就是任务执行的时间MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);queue.put(task);synchronized (this) {this.notify();}}
}public class ThreadDemo25 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("执行了任务A");}},1000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("执行了任务B");}},2000);}
}

运行结果:


重点理解

  • MyTask 类是用来描述一个任务的
  • 其中包含 要执行的任务内容 和 任务在什么时候执行

  • 正因为我们使用 优先级阻塞队列 来保存我们所有被注册的任务
  • 所以我们需要指定当前任务的优先级是什么
  • 此处我们任务的优先级是 越早执行的任务其优先级越高,即队首元素是时间最小的任务
  • 从而我们需要实现一个 Comparable 接口,并重写 compareTo 方法  

注意:

  • 此处的变量 time 为 long 类型,所以需要强制类型转换为 int 类型
  • 而且此处到底是(this.time - o.time)还是(o.time - this.time),我们仅试试就知道了,毕竟就这两种减法,不要去死记硬背

  • 关于这里的时间戳

  • 在 Java 中,System.currentTimeMillis() 是一个静态方法,它返回自1970年1月1日午夜(格林威治时间)以来当前时间的毫秒数
  • 即返回一个 long类型的值,表示当前时间与1970年1月1日午夜之间的毫秒数差,这个值可以用来计算时间间隔、时间戳等操作
  • 类似于输出 1626379152345

  • 这里我们为什么要引入 wait 和 notify 呢?

  • queue.put 操作是从 queue 中取出首元素
  • 此时的 queue.put 操作是放在 while 循环中的,因为我们想保证任务能够及时执行,所以不断的循环取出我们 queue 中的首元素,拿出来与当前的时间进行比较,以免错过任务的执行时间
  • 但是我们会发现,当距离队首元素执行任务还有很长一段时间的时候,queue 也会快速循环地将队首元素取出、比较、放回,那此时很显然就是在 忙等,CPU 不停地执行该循环操作,却毫无意义
  • 那么我们便可以直接引入wait 和 notify 来解决此情形
  • 当 queue 取出队首元素进行比较时,如果发现其还未到执行时间,那么将再次把该元素放回到 queue 中,然后再 wait 阻塞等待当前时间与首元素执行时间的时间差
  • 正因为在 wait 阻塞等待的期间中,可能还会插入新元素,并且不能保证该新元素是否会成为 queue 中新的首元素,所以在我们每插入一个新元素时,都需要进行 notify 一次,唤醒线程,然后继续执行 while 循环中的操作
  • 所以此处不能简单的使用 sleep 进行阻塞等待,因为无法感知新元素插入所导致的新改变

  • 使用 wait 的前提是得拥有锁对象,所以要进行加锁操作
  • 那么此时的 synchronized 有以下两种加锁方式,哪种更好呢?

  • 我们拿 方案二的加锁方式 进行分析
  • 当 线程t 执行 queue.take 语句时,此时便会将 queue 的队首元素取出来,然后准备进行比较操作
  • 假设队首元素的执行时间为 11点,且此时的时间为 9点,即该队首元素还未到执行时间,那么便将会把该元素重新放回到 queue 中
  • 如果此时的 线程t 正准备要执行 wait 进行阻塞等待时,CPU 转而执行其他,也就是在还未 wait 的情况下,又新增了一个任务,并且此时该任务的执行时间为 10点
  • 那么在新增任务的前提下,继续执行 wait 阻塞等待 2个小时 ,此时便会直接错过准备在10点 执行的新任务
  • 造成上述情况最主要的原因就是方案二并没有保证 take 和 wait 这两个操作执行的原子性,导致在执行这两个操作之间,可能会 put 进一个新任务
  • 所以我们可以将 synchronized 加锁的范围扩大,直接将 锁的范围扩大到 方案一,以此想保证 take 和 wait 操作的原子性

  • 但是仅这样我们能解决上述问题嘛?
  • 也就是能否保证在执行 take 和 wait 这两个操作时,执行这两个操作之间,不会再 put 进一个新任务
  • 显然仅通过扩大上述 synchronized 加锁的范围扩大,并不能完全保证,我们得保证再对锁对象加锁时,其 put 方法也需放入到 同一个锁对象的锁中,即将使用方案二

  • 也就是当 扫描线程t 对 锁对象进行了加锁操作,此时其他线程便不能调用被  同一个 锁对象 加了锁的代码块
  • 具体来说就是当主线程调用 schedule 方法准备执行 queue.put 语句插入新任务时, 便因为 扫描线程t 未释放 锁对象,所以主线程不能获取到锁对象,从而便会阻塞在 锁外,从而只要当 扫描线程t 释放了锁对象,主线程才能获取到锁对象,也就才能执行 queue.put 语句,才能往 queue 队列中插入新任务
  • 所以通过以上修改,我们便能很好的保证在执行 take 和 wait 这两个操作时,执行这两个操作之间,不会再 put 进一个新任务

  • 仔细思考上述代码,我们还会发现问题
  • 那就是通过  synchronized 加锁的范围扩大 和 把 put 方法也放入到 同一个锁对象的锁中 这两个操作,虽然解决了出现下图所示情况

  • 但是经过上述调整,该代码会存在 死锁 的情况
  • 假设此时 new 了一个 MyTimer 对象定时器
  • 那么此时就会初始化并调用 MyTimer 构造方法,构造方法就会创建一个线程t1,并开始执行其 run 方法,此时 线程t 便会拿到锁对象,程序进入 run 方法,但是由于 queue 队列中没有元素,因此就会在 queue.take 处阻塞等待,直到有任务放入队列中
  • 此时主线程通过调用 schedule 方法准备往 queue 队列中加入任务,但是由于 线程t 已经拿到锁对象了,且并未释放锁对象,所以此时在准备执行 queue.put 语句时,便会阻塞等待,所以此时 schedule 无法将任务 put 到 queue 队列中,这时 线程t 在阻塞等待,schedule 也在阻塞等待,就出现了死锁

模拟代码示例

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class Syn {private Object locker = new Object();BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();public Syn() {Thread t1 = new Thread(() -> {synchronized (locker) {try {queue.take();} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();}public void say() throws InterruptedException {synchronized (locker) {queue.put(Integer.valueOf("1"));}}
}public class TestSyn {public static void main(String[] args) throws InterruptedException {Syn syn = new Syn();Thread.sleep(1);syn.say();System.out.println("主线程打印 queue 中的值:"+ syn.queue.take());}
}

执行结果:

  • 我们发现新插入到 queue 中的值并未打印到控制台

通过 jconsole 观察线程情况:

  • 综上所述,为了 防止死锁的发生,我们又需将 queue.put 操作放到 锁外
  • 与上文通过  synchronized 加锁的范围扩大 和 把 queue.put 操作也放入到 同一个锁对象的锁中 这两个操作来保证 take 和 wait 这两个操作执行的原子性,也就是在执行 take 和 wait 这两个操作之间,不会再 put 进一个新任务
  • 从而这里 queue.take 无论是放在锁外还是锁内,都会引发问题

 自己实现的定时器代码最终代码版本

  • 使用 优先级阻塞队列 无论如何修改代码总会存在 问题,所以 我们直接转而使用 优先普通级队列
  • 不再使用 自带阻塞效果的 take 和 put 方法了
import java.util.PriorityQueue;// 创建一个类, 用来描述定时器中的一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 任务啥时候执行. 毫秒级的时间戳.private long time;// 任务具体是啥.private Runnable runnable;public MyTimerTask(Runnable runnable, long delay) {// delay 是一个相对的时间差. 形如 3000 这样的数值.// 构造 time 要根据当前系统时间和 delay 进行构造.time = System.currentTimeMillis() + delay;this.runnable = runnable;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}@Overridepublic int compareTo(MyTimerTask o) {// 认为时间小的, 优先级高. 最终时间最小的元素, 就会放到队首.// 怎么记忆, 这里是谁减去谁?? 不要记!! 记容易记错~~// 随便写一个顺序, 然后实验一下就行了.return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}// 定时器类的本体
class MyNewTimer {// 使用优先级队列, 来保存上述的 N 个任务private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 用来加锁的对象private Object locker = new Object();// 定时器的核心方法, 就是把要执行的任务添加到队列中.public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 每次来新的任务, 都唤醒一下之前的扫描线程. 好让扫描线程根据最新的任务情况, 重新规划等待时间.locker.notify();}}// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了, 是否应该执行; 一方面当任务到点之后,// 就要调用这里的 Runnable 的 Run 方法来完成任务public MyNewTimer() {// 扫描线程Thread t = new Thread(() -> {while (true) {try {synchronized (locker) {while (queue.isEmpty()) {// 注意, 当前如果队列为空, 此时就不应该去取这里的元素.// 此处使用 wait 等待更合适. 如果使用 continue, 就会使这个线程 while 循环运行的飞快,// 也会陷入一个高频占用 cpu 的状态(忙等).locker.wait();}MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 假设当前时间是 14:01, 任务时间是 14:00, 此时就意味着应该要执行这个任务了.// 需要执行任务.queue.poll();task.getRunnable().run();} else {// 让当前扫描线程休眠一下, 按照时间差来进行休眠.// Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}// 写一个定时器
public class ThreadDemo27 {public static void main(String[] args) {MyNewTimer timer = new MyNewTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1");}}, 1000);System.out.println("程序开始运行");}
}

原因如下图所示:

相关文章:

2023.10.22 关于 定时器(Timer) 详解

目录 引言 标准库定时器使用 自己实现定时器的代码 模拟实现的两大方面 核心思路 重点理解 自己实现的定时器代码最终代码版本 引言 定时器用于在 预定的时间间隔之后 执行特定的任务或操作 实例理解&#xff1a; 在服务器开发中&#xff0c;客户端向服务器发送请求&#…...

【STM32】GPIO控制LED(寄存器版)

在开始之前记得先准备好环境&#xff1a; STM32F103核心板下载教程.pdf 林何/STM32F103C8 - 码云 - 开源中国 (gitee.com) 一、STM32的GPIO模块数据手册详解 每个GPIO端口对应16个引脚&#xff0c;例GPIOA&#xff08;PA0~PA15&#xff09;内核cpu就可以通过APB2总线对寄存器…...

Spring Boot OAuth 2.0整合—高级配置

一、概述 HttpSecurity.oauth2Login() 为定制OAuth 2.0登录提供了大量的配置选项。主要的配置选项被分组到它们的协议端点对应处。 例如&#xff0c;oauth2Login().authorizationEndpoint() 允许配置授权端点&#xff0c;而 oauth2Login().tokenEndpoint() 允许配置令牌端点。…...

软考-虚拟专用网原理与应用

本文为作者学习文章&#xff0c;按作者习惯写成&#xff0c;如有错误或需要追加内容请留言&#xff08;不喜勿喷&#xff09; 本文为追加文章&#xff0c;后期慢慢追加 by 2023年10月 虚拟专用网概念 虚拟专用网&#xff08;Virtual Private Network&#xff09;是一种通过…...

clock_property 时钟的常用属性

get_property [get_clocks] property_option 1. period get_property [get_clocks] period 查询所有clock 的周期&#xff0c;如果存在loops会生成CTE_loops.rpt 2.clock_network_pins 查询clock所有的pins 3.generated_clocks_extended 查询clock分频产生的generate…...

平板有必要买触控笔吗?推荐的ipad手写笔

iPad之所以能吸引这么多人&#xff0c;主要是因为它的功能出色。用来画画、做笔记&#xff0c;也是一种不错的体验。但如果只是用来看电视和打游戏的话&#xff0c;那就真的有点大材小用了。如果你不需要昂贵的苹果电容笔&#xff0c;也不需要用来专业的绘图&#xff0c;那你可…...

Qt扫描-QMoive 理论总结

QMoive 理论总结 一、概述二、使用1. 使用2. 信号发出时机 三、控制的相关槽函数四、信号 一、概述 QMovie类是一个使用QImageReader播放 动画 的方便类。这个类用于显示没有声音的简单动画&#xff0c;一般即是 gif 动画。如果要显示视频和媒体内容&#xff0c;请使用Qt Mult…...

类似东郊到家预约家政保洁小程序搭建

随着生活水平的提高&#xff0c;人们对健康养生的需求越来越重视&#xff0c;按摩作为一种传统的养生方式&#xff0c;备受关注。为了方便用户快速、方便地预约按摩服务&#xff0c;本文将介绍一款按摩预约小程序的开发。 首先&#xff0c;我们通过市场调研和分析发现&#xf…...

[补题记录] Atcoder Beginner Contest 325(E、F)

URL&#xff1a;https://atcoder.jp/contests/abc325 目录 E Problem/题意 Thought/思路 Code/代码 F Problem/题意 Thought/思路 Code/代码 E Problem/题意 有一个二维矩阵&#xff0c;D[i][j] 表示从 i 到 j 的距离。从 i 到 j 有两种方式&#xff1a; 坐汽车&…...

1024啊啊啊啊啊啊

1024 程序员节快乐&#xff0c;没什么想发的&#xff0c;只是想要个1024胸章。...

淘宝商品详情API接口(H5端和APP端),淘宝详情页,商品属性接口,商品信息查询

一、接口参数说明&#xff1a;提取淘宝商品详情页各项数据&#xff0c;包含skuid、价格、收藏数、加购数、月销售量、主图、标题、详情页图片等页面上可以看奥的数据都可以拿到。 点击获取key和secret 二、使用场景 1、商品销售情况分析&#xff0c;根据销量调整活动方案&am…...

JVM的几个面试重点

JVM的内存区域划分 JVM类加载机制 前言 Java程序最开始是一个 .java 的文件&#xff0c;JVM把它编译成 .closs 文件&#xff08;字节码文件&#xff09;&#xff0c;运行 Java 程序&#xff0c; JVM 就会读取 .class 文件&#xff0c;把文件内容读取到内存中&#xff0c;构造出…...

[yolo系列:YOLOV7改进-添加CoordConv,SAConv.]

文章目录 概要CoordConvSAConv 概要 CoordConv&#xff08;Coordinate Convolution&#xff09;和SAConv&#xff08;Spatial Attention Convolution&#xff09;是两种用于神经网络中的特殊卷积操作&#xff0c;用于处理图像数据或其他多维数据。以下是它们的简要介绍&#x…...

【万字实操】可视化运维平台openGauss Datakit,带你轻松玩转openGauss 5.0

openGauss Datakit:openGauss社区推出的可视化的运维工具. 特性优势 初级用户学习openGauss门槛高让你望而却步&#xff1f;openGauss Datakit一键化安装企业版集群、监控、日志分析、SQL诊断&#xff0c;让你快速上手&#xff0c;快速部署&#xff0c;从容面对企业环境&#…...

《动手学深度学习 Pytorch版》 10.1 注意力提示

10.1.1 生物学中的注意力提示 “美国心理学之父” 威廉詹姆斯提出的双组件&#xff08;two-component&#xff09;框架&#xff1a; 非自主性提示&#xff1a;基于环境中物体的突出性和易见性 自主性提示&#xff1a;受到了认知和意识的控制 10.1.2 查询、键和值 注意力机制…...

C# 写入文件比较

数据长度&#xff1a;128188个long BinaryWriter每次写一个long 耗时14.7828ms StreamWriter每次写一个long 耗时44.0934 ms FileStream每次写一个long 耗时20.5142 ms FileStream固定chunk写入&#xff0c;循环操作数组&#xff0c;耗时13.4126 ms byte[] chunk new byte[d…...

医院设备利用(Use of Hospital Facilities, ACM/ICPC World Finals 1991, UVa212)rust解法

医院里有n&#xff08;n≤10&#xff09;个手术室和m&#xff08;m≤30&#xff09;个恢复室。每个病人首先会被分配到一个手术室&#xff0c;手术后会被分配到一个恢复室。从任意手术室到任意恢复室的时间均为t1&#xff0c;准备一个手术室和恢复室的时间分别为t2和t3&#xf…...

解决github ping不通的问题(1024程序员节快乐!

1024程序员节快乐&#xff01;&#xff08;随便粘贴一个文档&#xff0c;参加活动 解决github ping不通的问题 域名解析&#xff08;域名->IP&#xff09;&#xff1a;https://www.ipaddress.com/ Ubuntu平台 github经常ping不通或者访问缓慢&#xff0c;方法是更改host…...

QT基础 柱状图

目录 1.QBarSeries 2.QHorizontalBarSeries 3.QPercentBarSeries 4.QHorizontalPercentBarSeries 5.QStackedBarSeries 6.QHorizontalStackedBarSeries 从上图得知柱状的基类是QAbstractBarSeries&#xff0c;派生出来分别是柱状图的水平和垂直类&#xff0c;只是类型…...

微机原理与接口技术-第七章输入输出接口

文章目录 I/O接口概述I/O接口的典型结构基本功能 I/O端口的编址独立编址统一编址 输入输出指令I/O寻址方式I/O数据传输量I/O保护 16位DOS应用程序DOS平台的源程序框架DOS功能调用 无条件传送和查询传送无条件传送三态缓冲器锁存器接口电路 查询传送查询输入端口查询输出端口 中…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

LOOI机器人的技术实现解析:从手势识别到边缘检测

LOOI机器人作为一款创新的AI硬件产品&#xff0c;通过将智能手机转变为具有情感交互能力的桌面机器人&#xff0c;展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家&#xff0c;我将全面解析LOOI的技术实现架构&#xff0c;特别是其手势识别、物体识别和环境…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...