corn表达式是:由若干数字、空格、符号按一定的规则,组成的一组字符串,从而表达时间的信息。
好像和正则表达式有点类似哈,都是一个字符串表示一些信息。
Cron 表达式生成器: https://www.smart-tools.cn/cron
简介
Cron 表达式是一个具有时间含义的字符串,字符串以 5 或 6 个空格隔开,分为 6 或 7 个域,每一个域代表一种含义。 Cron 有如下两种语法格式:
秒 分 小时 日期 月份 星期
秒 分 小时 日期 月份 星期 年
即:秒 分 小时 日期 月份 星期 年(可为空)
主流工具
目前的 Cron 表达式主要有两类,分别是:
Linux crontab 命令 (Crontab 是linux系统自带的定时任务,用于设置周期性执行的本地脚本。Crontab的cron表达式只能精确到分钟。例如,* * * * ?)
Java Quartz(Quartz 是一个完全由 Java 编写的开源作业调度框架,为 Java 应用进行任务调度提供了简单却强大的机制。Quartz的cron表达式可以精确到秒。例如,* * * * * ?)
其中,Linux crontab 仅支持分钟级别的任务调度;Java Quartz 则可以秒级别的任务调度;
Linux crontab 中的 cron 语法规范
* * * * *
- - - - -
| | | | |
| | | | +----- 星期中星期几 (0 - 6) (星期天为0)
| | | +---------- 月份 (1 - 12)
| | +--------------- 一个月中的第几天 (1 - 31)
| +-------------------- 小时 (0 - 23)
+------------------------- 分钟 (0 - 59)
Java Quartz 中的 cron 语法规范
* * * * * *
- - - - - -
| | | | | |
| | | | | +----- 星期中星期几 (0 - 6) (星期天为0)
| | | | +---------- 月份 (1 - 12)
| | | +--------------- 一个月中的第几天 (1 - 31)
| | +-------------------- 小时 (0 - 23)
| +------------------------- 分钟 (0 - 59)
+------------------------------ 秒 (0 - 60)
域取值
下表为 Cron 表达式中每个域能够取的值以及支持的特殊字符。
域 | 是否必需 | 取值范围 | 特殊字符 |
秒 | 是 | [0, 59] | * , - / |
分钟 | 是 | [0, 59] | * , - / |
小时 | 是 | [0, 23] | * , - / |
日期 | 是 | [1, 31] | * , - / ? L W C |
月份 | 是 | [1, 12] | * , - / |
星期 | 是 | [1, 7] 其中 1 表示星期一,7 表示星期日。 | * , - / ? L C # |
年 | 否 | 留空,1970~2099 | , - * / |
特殊字符
Cron 表达式中的每个域都支持一定数量的特殊字符,每个特殊字符有其特殊含义。
特殊字符 | 含义 | 示例 |
* | 所有可能的值 | 在月份域中,* 表示每个月;在星期域中,* 表示星期的每一天。 |
, | 列出枚举值 | 在分钟域中,5,20 表示分别在 5 分钟和 20 分钟触发一次。 |
- | 范围 | 在分钟域中,5-20 表示从 5 分钟到 20 分钟之间每隔一分钟触发一次。 |
/ | 表示起始时间开始触发,然后每隔固定时间触发一次 | 在分钟域中,0/15 表示从第 0 分钟开始,每 15 分钟触发一次。在分钟域中3/20表示从第 3 分钟开始,每 20 分钟触发一次。 |
? | 不指定值,仅日期和星期域支持该字符 | 当日期或星期域其中之一被指定了值以后,为了避免冲突,需要将另一个域的值设为?。 |
L | 单词 Last 的首字母,表示最后一天,仅日期和星期域支持该字符 | 在日期域中,L 表示某个月的最后一天。在星期域中,L 表示一个星期的最后一天,也就是星期日(SUN)。如果在 L 前有具体的内容,例如,在星期域中的 6L,表示这个月的最后一个星期六。 |
W | 表示有效工作日(周一到周五),只能出现在日期域,系统将在离指定日期最近的有效工作日触发事件。W 字符寻找当前月份中最近有效工作日,连用字符 LW 时表示为指定月份的最后一个工作日。 | 在日期域中使用 5W, 如果 5 号是星期六,则将在最近的工作日星期五,即 4 日触发。如果 5 日是星期天,则将在最近的工作日星期一,即 6 日触发;如果 5 日在星期一到星期五中的一天,则就在 5 日触发。 |
# | 确定每个月第几个星期几,仅星期域支持该字符。 | 在星期域中,4#2表示某月的第二个星期四。 |
C | 这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。仅日期和星期域支持该字符。 | 日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。 |
秒
允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常
"*" 代表每隔1秒钟触发
"," 代表在指定的秒数触发,比如"0,15,45"代表0秒、15秒和45秒时触发任务
"-" 代表在指定的范围内触发,比如"25-45"代表从25秒开始触发到45秒结束触发,每隔1秒触发1次
"/" 代表触发步进(step),"/"前面的值代表初始值("*"等同"0"),后面的值代表偏移量,比如"0/20"或者"*/20"代表从0秒钟开始,每隔20秒钟触发1次,即0秒触发1次,20秒触发1次,40秒触发1次;"5/20"代表5秒触发1次,25秒触发1次,45秒触发1次;"10-45/20"代表在[10,45]内步进20秒命中的时间点触发,即10秒触发1次,30秒触发1次
分钟
允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常
"*" 代表每隔1分钟触发
"," 代表在指定的分钟触发,比如"10,20,40"代表10分钟、20分钟和40分钟时触发任务
"-" 代表在指定的范围内触发,比如"5-30"代表从5分钟开始触发到30分钟结束触 发,每隔1分钟触发
"/" 代表触发步进(step),"/"前面的值代表初始值("*"等同"0"),后面的值代表偏移量,比如"0/25"或者"*/25"代表从0分钟开始,每隔25分钟触发1次,即0分钟触发1次,第25分钟触发1次,第50分钟触发1次;"5/25"代表5分钟触发1次,30分钟触发1次,55分钟触发1次;"10-45/20"代表在[10,45]内步进20分钟命中的时间点触发,即10分钟触发1次,30分钟触发1次
小时
允许值范围: 0~23 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常
"*" 代表每隔1小时触发
"," 代表在指定的时间点触发,比如"10,20,23"代表10点钟、20点钟和23点触发任务
"-" 代表在指定的时间段内触发,比如"20-23"代表从20点开始触发到23点结束触发,每隔1小时触发
"/" 代表触发步进(step),"/"前面的值代表初始值("*"等同"0"),后面的值代表偏移量,比如"0/1"或者"*/1"代表从0点开始触发,每隔1小时触发1次;"1/2"代表从1点开始触发,以后每隔2小时触发一次
日期
允许值范围: 1~12 (JAN-DEC),不允许为空值,若值不合法,调度器将抛出SchedulerException异常
"*" 代表每个月都触发
"," 代表在指定的月份触发,比如"1,6,12"代表1月份、6月份和12月份触发任务
"-" 代表在指定的月份范围内触发,比如"1-6"代表从1月份开始触发到6月份结束触发,每隔1个月触发
"/" 代表触发步进(step),"/"前面的值代表初始值("*"等同"1"),后面的值代表偏移量,比如"1/2"或者"*/2"代表从1月份开始触发,每隔2个月触发1次;"6/6"代表从6月份开始触发,以后每隔6个月触发一次;"1-6/12"表达式意味着每年1月份触发
"C" 这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。比如“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。
星期
允许值范围: 1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六(一星期的最后一天),不允许为空值,若值不合法,调度器将抛出SchedulerException异常
"*" 代表每星期都触发;
"?" 与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义,以免引起冲突和混乱
"," 代表在指定的星期约定触发,比如"1,3,5"代表星期天、星期二和星期四触发
"-" 代表在指定的星期范围内触发,比如"2-4"代表从星期一开始触发到星期三结束触发,每隔1天触发
"/" 代表触发步进(step),"/"前面的值代表初始值("*"等同"1"),后面的值代表偏移量,比如"1/3"或者"*/3"代表从星期天开始触发,每隔3天触发1次;"1-5/2"表达式意味着在[1,5]范围内,每隔2天触发,即星期天、星期二、星期四触发
"L" 如果{星期}占位符如果是"L",即意味着星期的的最后一天触发,即星期六触发,L= 7或者 L = SAT,因此,"5L"意味着一个月的最后一个星期四触发
"#" 用来指定具体的周数,"#"前面代表星期,"#"后面代表本月第几周,比如"2#2"表示本月第二周的星期一,"5#3"表示本月第三周的星期四,因此,"5L"这种形式只不过是"#"的特殊形式而已
"C" 这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。比如:“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。
年份
允许值范围: 1970~2099 ,允许为空,若值不合法,调度器将抛出SchedulerException异常
"*"代表每年都触发
","代表在指定的年份才触发,比如"2011,2012,2013"代表2011年、2012年和2013年触发任务
"-"代表在指定的年份范围内触发,比如"2011-2020"代表从2011年开始触发到2020年结束触发,每隔1年触发
"/"代表触发步进(step),"/"前面的值代表初始值("*"等同"1970"),后面的值代表偏移量,比如"2011/2"或者"*/2"代表从2011年开始触发,每隔2年触发1次
注意:除了{日期}和{星期}可以使用"?"来实现互斥,表达无意义的信息之外,其他占位符都要具有具体的时间含义,且依赖关系为:年->月->日期(星期)->小时->分钟->秒数
Cron 表达式示例
示例 1 | 说明 |
0 15 10 ? * * | 每天上午 10:15 执行任务 |
0 15 10 * * ? | 每天上午 10:15 执行任务 |
0 0 12 * * ? | 每天中午 12:00 执行任务 |
0 0 10,14,16 * * ? | 每天上午 10:00 点、下午 14:00 以及下午 16:00 执行任务 |
0 0/30 9-17 * * ? | 每天上午 09:00 到下午 17:00 时间段内每隔半小时执行任务 |
0 * 14 * * ? | 每天下午 14:00 到下午 14:59 时间段内每隔 1 分钟执行任务 |
0 0-5 14 * * ? | 每天下午 14:00 到下午 14:05 时间段内每隔 1 分钟执行任务 |
0 0/5 14 * * ? | 每天下午 14:00 到下午 14:55 时间段内每隔 5 分钟执行任务 |
0 0/5 14,18 * * ? | 每天下午 14:00 到下午 14:55、下午 18:00 到下午 18:55 时间段内每隔 5 分钟执行任务 |
0 0 12 ? * WED | 每个星期三中午 12:00 执行任务 |
0 15 10 15 * ? | 每月 15 日上午 10:15 执行任务 |
0 15 10 L * ? | 每月最后一日上午 10:15 执行任务 |
0 15 10 ? * 6L | 每月最后一个星期六上午 10:15 执行任务 |
0 15 10 ? * 6#3 | 每月第三个星期六上午 10:15 执行任务 |
0 10,44 14 ? 3 WED | 每年 3 月的每个星期三下午 14:10 和 14:44 执行任务 |
cron 表达式用途
cron 表达式最主要的就是在程序中做一些定时任务,比如某些系统的报表数据,某些游戏的排行榜,由于这些数据量实时统计非常消耗程序性能,所以就每隔一段时间,通过自动任务跑一次,这样可以极大的提升用户浏览体验,要是在游戏里,还可以增加一种神秘感。
另外,某些具体点的数据拉取,比如你如果从事平台对接工作,要从某些平台下载你的订单,那么肯定是每隔多久抓一次。
又比如你写个爬虫,要实时的了解你的某些数据,然后从这些数据中反应你的情况。
一个简单的java项目示例
导入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
定义Job
定义一个HelloJob类实现Job接口
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("Say hello to Quartz {" + new Date() +"}");
}
}
测试
写个主函数,在主函数里面完成整个Quartz的操作过程
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class HelloQuartz {
public static void main(String[] args) throws SchedulerException {
//1.创建Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2.从工厂中获取调度器实例
Scheduler scheduler = sf.getScheduler();
//3.创建JobDetail
JobDetail jb = JobBuilder.newJob(HelloJob.class)
.withDescription("this is a job") //job的描述
.withIdentity("Job", "Group") //job 的name和group
.build();
//任务运行的时间,SimpleSchedle类型触发器有效
long time= System.currentTimeMillis() + 3*1000L; //3秒后启动任务
Date statTime = new Date(time);
//4.创建Trigger
//使用SimpleScheduleBuilder或者CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("")
.withIdentity("Trigger", "TriggerGroup")
//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
.startAt(statTime) //默认当前时间启动
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) // 10秒执行一次
.build();
//5.注册任务和定时器
scheduler.scheduleJob(jb, t);
//6.启动 调度器
scheduler.start();
}
}
运行截图:
一个简单的sprin boot项目示例
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
定义Job
定义一个HelloJob类实现Job接口
package com.example.demo;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("Say hello to Quartz {" + new Date() +"}");
}
}
测试
写个主函数,在主函数里面完成整个Quartz的操作过程
package com.example.demo;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 要配置Quartz的调度器Scheduler
// 调度器由SpringBoot管理,所以就变成了配置Spring
@Configuration
public class Demo {
// 配置的核心是向Spring容器保存一个job和保存一个Trigger
// 创建一个封装Job对象的类型JobDetail
// 使用@Bean注解标记的方法将这个对象保存到Spring容器
@Bean
public JobDetail addStock(){
//newJob方法就是在绑定要运行的Job接口实现类,需要实现类的反射做参数
return JobBuilder.newJob(HelloJob.class)
// 给当前JobDetail对象在调度环境中起名
.withIdentity("addStock")
// 即使没有触发器绑定当前JobDetail对象,也不会被删除
.storeDurably()
.build();
}
// 下面是触发器的声明,也会保存到Spring容器中
// 它能够设置job的运行时机
@Bean
public Trigger addStockTrigger(){
System.out.println("Trigger保存到Spring容器中");
// 定义Cron表达式
CronScheduleBuilder cron=
CronScheduleBuilder.cronSchedule("0/10 * * * * ?"); // 10秒执行一次
return TriggerBuilder.newTrigger()
// 绑定要运行的JobDetail对象
.forJob(addStock())
// 为触发器起名
.withIdentity("addStockTrigger")
// 绑定cron表达式
.withSchedule(cron)
.build();
}
}
运行截图:
Linux的crontab命令
在Linux中的cron表达式与上文所述有所不同,更准确地说是更加精简了。
crontab的命令构成为 cron表示式 + command,这里的cron表达
Minutes Hours DayOfMonth Month DayOfWeek
而操作符则有:
* / - ,
一起来看看几个例子:
1. 每晚的21:30重启smb
30 21 * * * /etc/init.d/smb restart
2. 每星期六的晚上11:00 pm重启smb
0 23 * * 6 /etc/init.d/smb restart
3. 晚上11点到早上7点之间,每隔一小时重启smb
0 23-7/1 * * * /etc/init.d/smb restart