【JavaEE】定时器

news2024/10/2 13:49:36

目录

前言

什么是定时器

如何使用java中的定时器

实现计时器

实现MyTimeTask类

Time类中存储任务的数据结构

实现Timer中的schedule方法

 实现MyTimer中的构造方法

处理构造方法中出现的线程安全问题

完整代码

 考虑在限时等待wait中能否用sleep替换

能否用PriorityBlockingQueue进行存储


在前面,已经讲解了几种常见设计模式,那么今天我们就来讲解一下定时器。

前言

在发送信息的时候,有时候不想要信息那么快就发送出去,而是在特定的时间再发送;或者我们在发送邮件时,当达到特定的时间时,就会自动发送电子邮件给用户,那么这里就需要用到定时器,那么定时器是什么呢?

什么是定时器

定时器是软件开发中的一个重要组件,类似于“闹钟”,能够在某个特定的时间执行一个或者多个任务,定时器是多线程中的一个案例,也是一个比较复杂且重要的案例。

如何使用java中的定时器

在java中,给我们提供了实现了的定时器包,我们可以直接使用。

java中给我们提供的定时器是Timer,我们在设置定时任务时,需要用到其中的schedule方法。

schedule包含两个参数:

第⼀个参数指定即将要执⾏的任务代码

第⼆个参数指定多⻓时间之后 执⾏(单位为毫秒)

示例:

class Demos{
    public static void main(String[] args) {
        // 创建一个Timer对象,用于调度定时任务
        Timer timer=new Timer();
        
        // 调度第一个定时任务,1秒后执行
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },1000);
        
        // 调度第二个定时任务,2秒后执行
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },2000);
        
        // 调度第三个定时任务,3秒后执行
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },3000);
    }

}

实现计时器

我们从上述代码中可以看出,要实现一个定时器,需要实现以下:

  1. 实现一个任务(Task)类
  2. 实现一个Timer类用来存放任务

实现MyTimeTask类

/**
 * TimeTask类用于封装一个延迟执行的任务
 * 它包含一个需要执行的任务和一个延迟时间
 */
class TimeTask{
    //需要执行的任务
    private Runnable runnable;
    //任务等待执行的时间点,以毫秒为单位
    private long time;

    /**
     * 构造函数,用于创建一个TimeTask对象
     * @param runnable 需要延迟执行的任务,类型为Runnable
     * @param delay    任务延迟执行的时间,单位为毫秒
     */
    public TimeTask(Runnable runnable,long  delay){
        this.runnable=runnable;
        //计算任务应该执行的时间点
        this.time=System.currentTimeMillis()+delay;
    }

    /**
     * 返回任务的等待执行时间
     * @return 任务等待执行的时间点,以毫秒为单位
     */
    public long getTime(){
        return this.time;
    }

    /**
     * 执行当前任务
     * 调用构造时传入的Runnable对象的run方法来执行任务
     */
    public void run(){
        this.runnable.run();
    }
}

Time类中存储任务的数据结构

我们在存储任务的时候,需要根据等待时间来存储,时间短的优先取出来,那么我们就可以使用优先级队列,创建一个小根堆,时间最短的放在堆顶。

    private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>();

但是我们这里要怎么比较呢?我们可以实现Comparable接口重写compareTo方法来进行比较,或者创建一个类来实现Comparator接口重写compare方法来进行比较。

实现Timer中的schedule方法

当我们解决了在任务在优先级队列中如何进行比较存储任务的问题之后,那么就可以在MyTimer中来实现schedule方法。根据schedule方法的参数创建一个MyTimeTask类,并将其添加到优先级队列中。

/**
 * 将一个 Runnable 任务安排在指定的延迟时间后执行
 * 
 * @param runnable 要执行的任务
 * @param delay    相对于现在的时间延迟,单位为毫秒
 * 
 * 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行
 */
public void schedule(Runnable runnable, long delay) {
    // 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间
    MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);
    // 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行
    pq.add(myTimeTask);
}

 实现MyTimer中的构造方法

通过实例化一个线程,在这个线程中,通过多次扫描优先级队列中的元素,判断堆顶元素是否到达了等待时长,若是,则取出并执行。

注意:这里不能直接poll取出栈顶元素,若栈顶的任务等待时间还未到达,则继续循环。

 /**
     * 构造函数,初始化MyTimer对象
     * 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务
     */
    public MyTimer(){
        // 创建一个新的线程来执行定时任务
        Thread t=new Thread(()->{
            // 无限循环,持续检查任务队列
            while(true) {
                if(pq.isEmpty()){
                    continue;
                }
                // 查看在栈顶的任务
                MyTimeTask task = pq.peek();
                // 比较栈顶的任务与当前时间点的比较
                if (System.currentTimeMillis() >= task.getTime()) {
                    // 当前时间已达到任务设定时间,移除任务并执行
                    pq.poll();
                    task.run();
                }else {// 如果栈顶的任务时间大于当前时间,则继续循环
                    // 继续检查下一个任务,或在没有到达时间的任务时继续等待
                    continue;
                }
            }
        });
        // 启动线程
        t.start();
    }

处理构造方法中出现的线程安全问题

在上述代码中,能看出哪里存在线程安全问题吗?

优先级队列并不是一个线程安全的队列,我们在瞥(peek)取(poll)的时候,可能会出现线程安全问题,若是在多线程环境中,当线程1刚peek了堆顶任务,但此时切换到线程2,线程2同样peek堆顶任务,并刚好到了等待时间,此时就会执行并且删除栈顶任务。此时又切换到线程1,但此时线程1peek的堆顶任务已经被poll掉了,此时如果再执行,就会再次删除堆顶任务,导致出现线程安全问题。

所以,这里我们需要对peek和poll操作进行加锁。

    /**
     * 构造函数,初始化MyTimer对象
     * 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务
     */
    public MyTimer(){
        // 创建一个新的线程来执行定时任务
        Thread t=new Thread(()->{
            // 无限循环,持续检查任务队列
            while(true) {
                synchronized (lock) {
                    // 如果任务队列为空,则跳过当前循环
                    if (pq.isEmpty()) {
                        continue;
                    }
                    // 查看在栈顶的任务
                    MyTimeTask task = pq.peek();
                    // 比较栈顶的任务与当前时间点的比较
                    if (System.currentTimeMillis() >= task.getTime()) {
                        // 当前时间已达到任务设定时间,移除任务并执行
                        pq.poll();
                        task.run();
                    } else {// 如果栈顶的任务时间大于当前时间,则继续循环
                        // 继续检查下一个任务,或在没有到达时间的任务时继续等待
                        continue;
                    }
                }
            }
        });
        // 启动线程
        t.start();
    }

 这里还有什么能优化的吗?

我们可以看到,当优先级队列中不为空,但此时堆顶任务的等待时间还没到,此时就会进入else分支执行continue,但一直重复这样操作,可能会造成不断检查,cpu使用率过高。那么我们就可以使用带参数的wait来进行限时等待,当达到时限时,会自动唤醒线程。

同理的,在判断队列是否为空时,我们可以设置不带参数的wait,等待唤醒。

    /**
     * 构造函数,初始化MyTimer对象
     * 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务
     */
    public MyTimer(){
        // 创建一个新的线程来执行定时任务
        Thread t=new Thread(()->{
            // 无限循环,持续检查任务队列
            while(true) {
                synchronized (lock) {
                    // 如果任务队列为空,则跳过当前循环
                    while (pq.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 查看在栈顶的任务
                    MyTimeTask task = pq.peek();
                    // 比较栈顶的任务与当前时间点的比较
                    if (System.currentTimeMillis() >= task.getTime()) {
                        // 当前时间已达到任务设定时间,移除任务并执行
                        pq.poll();
                        task.run();
                    } else {// 如果栈顶的任务时间大于当前时间,则继续循环
                        try {
                            lock.wait(task.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        // 启动线程
        t.start();
    }

既然这里等待,那么我们就需要有人来唤醒wait,所以我们在schedule方法中也需要进行加锁,并且在添加完任务后,调用notify来进行通知。

/**
     * 将一个 Runnable 任务安排在指定的延迟时间后执行
     *
     * @param runnable 要执行的任务
     * @param delay    相对于现在的时间延迟,单位为毫秒
     *
     * 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行
     */
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {
            // 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间
            MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);
            // 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行
            pq.add(myTimeTask);
            // 唤醒可能在等待执行任务的线程
            lock.notify();
        }
    }

 MyTimer优化到这里,其实已经优化好了。

完整代码

package Threads;

import java.util.Comparator;
import java.util.PriorityQueue;

/**
 * TimeTask类用于封装一个延迟执行的任务
 * 它包含一个需要执行的任务和一个延迟时间
 */
class TimeTask implements Comparable<TimeTask>{
    //需要执行的任务
    private Runnable runnable;
    //任务等待执行的时间点,以毫秒为单位
    private long time;

    /**
     * 构造函数,用于创建一个TimeTask对象
     * @param runnable 需要延迟执行的任务,类型为Runnable
     * @param delay    任务延迟执行的时间,单位为毫秒
     */
    public TimeTask(Runnable runnable,long  delay){
        this.runnable=runnable;
        //计算任务应该执行的时间点
        this.time=System.currentTimeMillis()+delay;
    }

    /**
     * 返回任务的等待执行时间
     * @return 任务等待执行的时间点,以毫秒为单位
     */
    public long getTime(){
        return this.time;
    }

    /**
     * 执行当前任务
     * 调用构造时传入的Runnable对象的run方法来执行任务
     */
    public void run(){
        this.runnable.run();
    }


    @Override
    public int compareTo(TimeTask o) {
        return (int) (this.time- o.getTime());

    }
}
class MyTimer{
    //private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>(new compareTimeTask());
    private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>();
    static Object lock=new Object();

    /**
     * 构造函数,初始化MyTimer对象
     * 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务
     */
    public MyTimer(){
        // 创建一个新的线程来执行定时任务
        Thread t=new Thread(()->{
            // 无限循环,持续检查任务队列
            while(true) {
                synchronized (lock) {
                    // 如果任务队列为空,则跳过当前循环
                    while (pq.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 查看在栈顶的任务
                    MyTimeTask task = pq.peek();
                    // 比较栈顶的任务与当前时间点的比较
                    if (System.currentTimeMillis() >= task.getTime()) {
                        // 当前时间已达到任务设定时间,移除任务并执行
                        pq.poll();
                        task.run();
                    } else {// 如果栈顶的任务时间大于当前时间,则继续循环
                        try {
                            lock.wait(task.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        // 启动线程
        t.start();
    }




    /**
     * 将一个 Runnable 任务安排在指定的延迟时间后执行
     *
     * @param runnable 要执行的任务
     * @param delay    相对于现在的时间延迟,单位为毫秒
     *
     * 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行
     */
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {
            // 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间
            MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);
            // 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行
            pq.add(myTimeTask);
            // 唤醒可能在等待执行任务的线程
            lock.notify();
        }
    }

}
class Demo{
    public static void main(String[] args) {
        MyTimer timer=new MyTimer();
        timer.schedule(()->{
            System.out.println("Hello World1");
        },1000);
        timer.schedule(()->{
            System.out.println("Hello World2");
        },2000);
        timer.schedule(()->{
            System.out.println("Hello World3");
        },3000);
    }
}
class compareTimeTask implements Comparator<MyTimeTask>{

    @Override
    public int compare(MyTimeTask o1, MyTimeTask o2) {
        return (int) (o1.getTime()-o2.getTime());
    }
}

测试一下

 考虑在限时等待wait中能否用sleep替换

 Thread.sleep(task.getTime()-System.currentTimeMillis());

 这里为什么不用sleep呢?

在前面线程安全问题中已经讲解了wait和sleep的区别,在这里,如果我们使用sleep,会导致拉着锁一起进入睡眠,导致其他线程拿不到锁对象,无法进行加锁。

这会导致我们想要调用schedule方法添加任务时,拿不到锁对象。

能否用PriorityBlockingQueue进行存储

在前面,我们用的是优先级队列PriorityBlockingQueue来存储任务,但如果我们用PriorityBlockingQueue呢?

如果我们使用PriorityBlockingQueue,那么我们的方法也需要改成take(取)和put(存),才能用阻塞等待的效果。

修改代码:

package Threads;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * TimeTask类用于封装一个延迟执行的任务
 * 它包含一个需要执行的任务和一个延迟时间
 */
class TimeTask implements Comparable<TimeTask>{
    //需要执行的任务
    private Runnable runnable;
    //任务等待执行的时间点,以毫秒为单位
    private long time;

    /**
     * 构造函数,用于创建一个TimeTask对象
     * @param runnable 需要延迟执行的任务,类型为Runnable
     * @param delay    任务延迟执行的时间,单位为毫秒
     */
    public TimeTask(Runnable runnable,long  delay){
        this.runnable=runnable;
        //计算任务应该执行的时间点
        this.time=System.currentTimeMillis()+delay;
    }

    /**
     * 返回任务的等待执行时间
     * @return 任务等待执行的时间点,以毫秒为单位
     */
    public long getTime(){
        return this.time;
    }

    /**
     * 执行当前任务
     * 调用构造时传入的Runnable对象的run方法来执行任务
     */
    public void run(){
        this.runnable.run();
    }


    @Override
    public int compareTo(TimeTask o) {
        return (int) (this.time- o.getTime());

    }
}
class MyTimer{
    //private PriorityQueue<MyTimeTask> pq=new PriorityQueue<>(new compareTimeTask());
    private PriorityBlockingQueue<MyTimeTask> pq=new PriorityBlockingQueue<>(100);
    static Object lock=new Object();

    /**
     * 构造函数,初始化MyTimer对象
     * 创建并启动一个线程,用于持续检查并执行已到达设定时间的任务
     */
    public MyTimer(){
        // 创建一个新的线程来执行定时任务
        Thread t=new Thread(()->{
            // 无限循环,持续检查任务队列
            while(true) {
                synchronized (lock) {
                    // 如果任务队列为空,则跳过当前循环
                    while (pq.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 查看在栈顶的任务
                    MyTimeTask task = null;
                    try {
                        task = pq.take();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    // 比较栈顶的任务与当前时间点的比较
                    if (System.currentTimeMillis() >= task.getTime()) {
                        // 当前时间已达到任务设定时间,移除任务并执行
                        task.run();
                    } else {// 如果栈顶的任务时间大于当前时间,则继续循环
                        pq.put(task);
                        try {
                            lock.wait(task.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        // 启动线程
        t.start();
    }




    /**
     * 将一个 Runnable 任务安排在指定的延迟时间后执行
     *
     * @param runnable 要执行的任务
     * @param delay    相对于现在的时间延迟,单位为毫秒
     *
     * 注意:这个方法使用一个优先队列(pq)来管理这些被安排的任务,确保它们在指定的延迟后被执行
     */
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {
            // 创建一个 MyTimeTask 对象,它包含了 Runnable 任务和延迟时间
            MyTimeTask myTimeTask = new MyTimeTask(runnable, delay);
            // 将 MyTimeTask 对象添加到优先队列 pq 中,以便在未来的某个时间执行
            pq.put(myTimeTask);
            // 唤醒可能在等待执行任务的线程
            lock.notify();
        }
    }

}
class Demo{
    public static void main(String[] args) {
        MyTimer timer=new MyTimer();
        timer.schedule(()->{
            System.out.println("Hello World1");
        },1000);
        timer.schedule(()->{
            System.out.println("Hello World2");
        },2000);
        timer.schedule(()->{
            System.out.println("Hello World3");
        },3000);
    }
}
class compareTimeTask implements Comparator<MyTimeTask>{

    @Override
    public int compare(MyTimeTask o1, MyTimeTask o2) {
        return (int) (o1.getTime()-o2.getTime());
    }
}

由于take会触发阻塞等待,而后面的wait也会,这里加了两次锁,容易引出线程安全问题,所以我们建议使用一个锁对象lock,来进行加锁就行。而不使用无界阻塞队列。


以上就是本篇所有内容~

若有不足,欢迎指正~

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

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

相关文章

Linux网络——深入理解 epoll

目录 一、epoll 模型 1.1 前导知识 1.1.1 宏 offsetof 1.1.2 手动计算 1.2 epoll 模型 二、 epoll 工作模式 2.1 水平触发 特点&#xff1a; 2.2 边缘触发 特点&#xff1a; 边缘触发模式中的循环读取 结合非阻塞模式的优势 一、epoll 模型 经过了之前的学习&#…

什么是容器查询?分享 1 段优质 CSS 代码片段!

本内容首发于工粽号&#xff1a;程序员大澈&#xff0c;每日分享一段优质代码片段&#xff0c;欢迎关注和投稿&#xff01; 大家好&#xff0c;我是大澈&#xff01; 本文约 700 字&#xff0c;整篇阅读约需 1 分钟。 今天分享一段优质 CSS 代码片段&#xff0c;使用容器查询…

【算法设计题】实现以字符串形式输入的简单表达式求值,第2题(C/C++)

目录 第2题 实现以字符串形式输入的简单表达式求值 得分点&#xff08;必背&#xff09; 题解 1. 初始化和变量定义 2. 获取第一个数字并存入队列 3. 遍历表达式字符串&#xff0c;处理运算符和数字 4. 初始化 count 并处理加减法运算 代码详解 &#x1f308; 嗨&#xf…

你还在为PDF文件烦恼吗?试试这四款合并工具吧!

每天应对工作都是一个头两个大的&#xff0c;其中pdf的文件问题就是恼人的工作量之一了&#xff0c;这几年的工作经历下来也找了各种可以帮助解决PDF文件问题的工具&#xff0c;好在使用了一些助力我高效工作的软件&#xff0c;今天针对其中遇到的解决pdf合并问题的四款宝藏工具…

当Vercel的域名验证规则碰上JPDirect这种不配合的同学把我的脑袋擦出了火星子

文章目录 前言问题简单说明Vercel主要功能和特点 JPDirectNameServers解决方案 总结 前言 处理域名转移这件事已经过去好几天&#xff0c;终于抽出点时间来总结一下&#xff0c;解决这件事大概花了2周多时间&#xff0c;因为时差的原因导致沟通缓慢&#xff0c;今天准备长话短…

【leetcode】平衡二叉树、对称二叉树、二叉树的层序遍历(广度优先遍历)(详解)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…

Zookeeper未授权访问漏洞

Zookeeper是分布式协同管理工具&#xff0c;常用来管理系统配置信息&#xff0c;提供分布式协同服务。Zookeeper的默认开放端口是2181。Zookeeper安装部署之后默认情况下不需要任何身份验证&#xff0c;造成攻击者可以远程利用Zookeeper&#xff0c;通过服务器收集敏感信息或者…

通信原理实验——PCM编译码

PCM编译码 实验目的 理解PCM编译码原理及PCM编译码性能熟悉PCM编译码专用集成芯片的功能和使用方法及各种时钟关系熟悉语音数字化技术的主要指标及测量方法 主要仪器设备及软件 硬件&#xff1a;多功能实验箱、示波器、导线 软件&#xff1a;无 实验原理 1. 抽样信号的量…

锅总浅析SRE

SRE简介 SRE&#xff08;Site Reliability Engineering&#xff0c;站点可靠性工程&#xff09;是由Google开发的一种运维理念和实践方法&#xff0c;其核心思想是用软件工程的方式来管理和运维系统&#xff0c;以提高系统的可靠性、效率和可扩展性。 SRE的核心理念 自动化&…

【Slf4j】项目中使用 slf4j 的好处

前言 背景 项目依赖了三方包&#xff0c;三方包有日志打印的代码。需要将三方包的日志打出来。问题 是怎么做到项目的日志格式和依赖中的日志格式保持一致的&#xff1f;结论 查阅资料后&#xff0c;发现是 slf4j 帮忙做了桥接。这里做下记录。 实验 starter 依赖 log4j主项…

【全网最全】2024年第五届“华数杯”全国大学生数学建模竞赛完整思路解析+代码+论文

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

常见的MySQL数据库面试题

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 总结一下mysql中常…

【最新】精选8家优秀大学生AI论文写作网站

在当前的学术环境中&#xff0c;AI论文写作平台为大学生提供了极大的便利和高效性。以下是8家优秀的AI论文写作网站推荐&#xff1a; 一、千笔-AIPassPaPer 千笔-AIPassPaPer是一款AI原创论文写作平台&#xff0c;能够在10分钟内产出3万字的内容&#xff0c;并提供真实网络数据…

广州城市信息模型(CIM)白皮书学习

CIM平台定义 以建筑信息模型(BIM)、地理信息系统(GIS)、物联网(IoT)等技术为基础&#xff0c;整合城市地上地下、室内室外、历史现状未来多维多尺度信息模型数据和城市感知数据&#xff0c;构建起三维数字空间的城市信息有机综合体。 广州CIM平台建设历程 2019 年 6 月住房和…

关于手机中的红外遥控

在手机电路中&#xff0c;有这么不起眼的一部分&#xff0c;虽看似简单&#xff0c;但是却给我们的生活在一定程度上带来了极大的便捷-红外遥控部分。 其置于手机顶部&#xff0c;并在壳体处挖开一个小孔&#xff0c;用于红外信号对外界的传递。如果你感兴趣的话&#xff0c;不…

【时时三省】unity test 测试框架 使用 code blocks 移植(核心文件:unity.c)

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 目录 1&#xff0c;使用 Code::Blocks 17.12 创建工程 2&#xff0c;移植文件至该工程下&#xff1a; 移入的文件为: 被移入的文件介绍&#xff1a; 更改代码&#xff1a; 向工程添加文…

[数据集][目标检测]生产线上金属罐易拉罐正反面检测数据集VOC+YOLO格式2715张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2715 标注数量(xml文件个数)&#xff1a;2715 标注数量(txt文件个数)&#xff1a;2715 标注…

【2024年华数杯全国大学生数学建模竞赛】C题:老外游中国 问题思路分析及Python代码实现

【2024 年华数杯全国大学生数学建模竞赛】C题&#xff1a;老外游中国 问题思路分析及Python代码实现 1 题目 最近&#xff0c;“city 不 city”这一网络流行语在外国网红的推动下备受关注。随着我国过境免签政策的落实&#xff0c;越来越多外国游客来到中国&#xff0c;通过网…

【Nuxt】约定式路由和内置组件

约定式路由 手动创建&#xff1a; 或者还可以使用终端创建页面&#xff1a;nuxi-add-page npx nuxi add page about — about.vue npx nuxi add page about/index — about/index.vue <NuxtLink to"/"><button>Home</button></NuxtLink><…

宅家也能高效办公?试试这四款款远程控制神器!

因为工作时不时需要出差 &#xff0c;所以自打有出差以来遇到同事需要远程求助的情况都会想到远程控制电脑的方式&#xff0c;不仅仅解决了异地无法处理的情况&#xff0c;还能够及时快速并且零成本处理问题&#xff0c;所以今天就整理了四款很适合打工人的远程控制电脑的工具&…