1. 概述

使用cron调度器,我们可以自动化处理那些原本需要手动执行的重复性任务。此外,cron表达式能让我们精确指定任务的执行时间。

在Java中调度任务时,我们通常使用Quartz库——这是一个完全用Java编写的开源任务调度解决方案。如果使用Spring框架,还可以通过@Scheduled注解轻松实现任务调度。

虽然cron表达式功能强大,但其语法有时会让人困惑甚至望而生畏。

本文将深入探讨cron表达式中星号(*)和问号(?)这两个特殊字符的区别。

2. Cron表达式中的字段

在深入讨论前,先了解cron表达式中的字段结构。

在Quartz中,cron表达式是一个由空格分隔的字符串,最多包含七个字段,每个字段代表特定的日期时间单位:

字段 必需 允许值 允许的特殊字符
秒(Seconds) 0-59 , - * /
分(Minutes) 0-59 , - * /
时(Hours) 0-23 , - * /
日(Day of Month) 1-31 , - * / ? L W
月(Month) 0-11 (或 JAN-DEC) , - * /
周(Day of Week) 1-7 (或 SUN-SAT) , - * / ? L C #
年(Year) 1970-2099 (或空) , - * /

如上表所示,除年份字段外,所有字段都是必需的。若未指定年份,任务将每年执行。

Unix cron表达式的语法略有不同:

字段 必需 允许值 允许的特殊字符
分(Minutes) 0-59 , - * /
时(Hours) 0-23 , - * /
日(Day of Month) 1-31 , - * /
月(Month) 1-12 (或 JAN-DEC) , - * /
周(Day of Week) 0-6 (或 SUN-SAT) , - * /

Unix cron表达式由五个字段组成,后接要执行的命令。与Quartz不同,它没有秒和年字段,专注于当前年度的任务调度。

⚠️ 注意:Unix cron表达式不允许使用问号(?)符号。

后续内容将主要围绕Quartz库的cron表达式展开。

3. 问号(?)在Cron表达式中的用法

现在来看问号(?)在cron表达式中的作用。简单来说,它表示不指定具体值

只能用于"日(Day of Month)"和"周(Day of Week)"字段。

但需注意:日字段和周字段是互斥的。也就是说,不能在同一个表达式中同时为这两个字段指定值

例如,以下表达式会报错:

0 30 10 1 OCT 2 2023

为便于理解,用表格展示:

0 30 10 1 OCT 2 2023

我们同时设置了日字段和周字段,这在Quartz中是不支持的。

即使日期恰好匹配正确的星期几,表达式仍然无效:

0 30 10 30 OCT 2 2023

2023年10月30日确实是星期一,但该表达式依然非法。

由于必须为这两个字段设置值,我们需要在其中之一使用问号(?)表示未设置。 使用问号的字段将被忽略:

0 0 0 30 OCT ?

这个例子中,任务将在每年10月30日的午夜执行。

此外,问号(?)在单个cron表达式中只能出现一次。 同时使用两个问号也会报错:

0 30 * ? OCT ?

4. 星号(*)在Cron表达式中的用法

星号(*)在cron表达式中表示所有可能的值。换句话说,用它来指定某个字段的所有允许值。

与问号不同,星号可用于cron表达式中的任何字段。

例如,创建一个在小时字段使用星号的表达式:

0 30 * 1 OCT ?

用表格展示:

0 30 * 1 OCT ?

该任务在10月1日的每个小时(0-23时)的30分0秒执行。

星号也可用于多个字段:

* * * * OCT ?

这个任务在10月的每秒都会执行。

4.1. Linux Cron中的日和周字段

在Linux cron中,日字段和周字段的行为与Quartz不同:

  1. 它们不是互斥的,可以在同一个表达式中同时设置值。
  2. 当两个字段都包含非星号值时,它们形成"并集"关系
    30 10 1 10 5
    
    这个任务在10月1日和每个星期五的10:30执行。
  3. 当其中一个字段以星号开头时,它们形成"交集"关系
    30 10 */1 * 1
    
    这个任务只在星期一的每天(即每个星期一)的10:30执行。

5. 星号(*)和问号(?)的对比

总结一下两个特殊字符的主要区别:

特性 星号 (*) 问号 (?)
含义 表示字段的所有允许值 表示不指定具体值
可用字段 任何字段 仅限日和周字段
使用目的 指定字段的所有值 将字段留空
出现次数 同一表达式可多次出现 每个表达式只能出现一次

6. 结论

本文深入探讨了cron表达式中星号(*)和问号(?)的区别。

简单粗暴地总结:

  • 星号(*)用于指定某个字段的所有允许值
  • 问号(?)表示不指定值,且只能用于日和周字段

由于Quartz不支持同时设置日和周字段,我们必须在其中之一使用问号(?)来留空。这个设计虽然有点反直觉,但理解后就能避免常见的调度踩坑问题。


原始标题:Differences Between * and ? in Cron Expressions