很久没有写博客了,去年一整年都在忙一个重要的项目,现在回过头来有点时间,优化了一些业务流程、代码规范。在优化的时候就看到了很多"魔法数",项目里充斥着60 * 1000L、300000、120000等时间"魔法数",虽然不会看不懂,但是阅读起来始终有点障碍,还容易搞错单位。于是就统一使用TimeUnit改了一下,点进去之后发现是"c/c++风格"Java代码。回顾一下大学时代的C语言时也一起学习一下Doug Lea大佬的编程思维。
一、TimeUnit概览
TimeUnit
是java.util.concurrent
包中的枚举类,定义了7种时间单位:
Java
public enum TimeUnit {
NANOSECONDS(TimeUnit.NANO_SCALE),
MICROSECONDS(TimeUnit.MICRO_SCALE),
MILLISECONDS(TimeUnit.MILLI_SCALE),
SECONDS(TimeUnit.SECOND_SCALE),
MINUTES(TimeUnit.MINUTE_SCALE),
HOURS(TimeUnit.HOUR_SCALE),
DAYS(TimeUnit.DAY_SCALE);
}
核心设计是基于纳秒的统一换算:
Java
private static final long NANO_SCALE = 1L;
private static final long MICRO_SCALE = 1000L * NANO_SCALE;
private static final long MILLI_SCALE = 1000L * MICRO_SCALE;
private static final long SECOND_SCALE = 1000L * MILLI_SCALE;
private static final long MINUTE_SCALE = 60L * SECOND_SCALE;
private static final long HOUR_SCALE = 60L * MINUTE_SCALE;
private static final long DAY_SCALE = 24L * HOUR_SCALE;
每个时间单位通过scale
值定义相对于纳秒的倍数。这种设计将时间单位抽象为数学关系,确保所有转换均为整数运算,以纳秒为基准,粒度最细,覆盖所有单位;
二、构造方法
TimeUnit
通过初始化时就把下面的字段计算好关键字段,优化性能
Java
private final long scale; // 基础转换倍数
private final long maxNanos; // 纳秒转换溢出边界
private final long maxMicros; // 微秒转换溢出边界
private final long maxMillis; // 毫秒转换溢出边界
private final long maxSecs; // 秒转换溢出边界
private final long microRatio; // 微秒转换比
private final int milliRatio; // 毫秒转换比
private final int secRatio; // 秒转换比
private TimeUnit(long s) {
this.scale = s;
this.maxNanos = Long.MAX_VALUE / s; // 防止溢出
long ur = (s >= MICRO_SCALE) ? (s / MICRO_SCALE) : (MICRO_SCALE / s);
this.microRatio = ur;
this.maxMicros = Long.MAX_VALUE / ur;
long mr = (s >= MILLI_SCALE) ? (s / MILLI_SCALE) : (MILLI_SCALE / s);
this.milliRatio = (int)mr;
this.maxMillis = Long.MAX_VALUE / mr;
long sr = (s >= SECOND_SCALE) ? (s / SECOND_SCALE) : (SECOND_SCALE / s);
this.secRatio = (int)sr;
this.maxSecs = Long.MAX_VALUE / sr;
}
通过初始化计算转换比和边界值,利用极少量的内存换取O(1)时间复杂度的转换。
三、放大与缩小的艺术
toSeconds方法
TimeUnit
的核心转换方法(如toSeconds
、toMillis
)通过比较scale
与目标单位scale
的大小,优雅地决定是"放大"还是"缩小"。以下以toSeconds
为例:
Java
public long toSeconds(long duration) {
long s, m;
if ((s = scale) <= SECOND_SCALE)
return (s == SECOND_SCALE) ? duration : duration / secRatio;
else if (duration > (m = maxSecs))
return Long.MAX_VALUE;
else if (duration < -m)
return Long.MIN_VALUE;
else
return duration * secRatio;
}
每个类型的TimeUnit
的scale
表示其相对于纳秒的倍数,SECOND_SCALE
为1_000_000_000
(1秒的纳秒数)。通过比较scale
与SECOND_SCALE
,即可判断转换方向:
- scale < SECOND_SCALE (如
MILLISECONDS
):当前单位比秒小,输入duration
更精细,需缩小(除法) ,用secRatio
(如1000)除以输入值。 - scale == SECOND_SCALE (如
SECONDS
):单位相同,直接返回duration
。 - scale > SECOND_SCALE (如
HOURS
):当前单位比秒大,输入duration
更粗糙,需放大(乘法) ,用secRatio
(如3600)乘以输入值。
转换方向 | 数学关系 | 算法选择 | 数学原理 |
---|---|---|---|
细→粗 | scale < SECOND_SCALE |
除法 | 粗单位包含多个细单位,需整除 |
同级 | scale == SECOND_SCALE |
直接返回 | 无需转换 |
粗→细 | scale > SECOND_SCALE |
乘法 | 粗单位数值需倍增为细单位 |
TimeUnit.MILLISECONDS.toSeconds(1)
scale = MILLI_SCALE = 1_000_000
SECOND_SCALE = 1_000_000_000
- 判断:
scale < SECOND_SCALE
→ 缩小 - 计算:
secRatio = SECOND_SCALE / MILLI_SCALE = 1000
- 结果:
1 / 1000 = 0
(1毫秒不足1秒)
TimeUnit.SECONDS.toSeconds(1)
scale = SECOND_SCALE = 1_000_000_000
- 判断:
scale == SECOND_SCALE
→ 相等 - 结果:直接返回
1
TimeUnit.HOURS.toSeconds(1)
scale = HOUR_SCALE = 3_600 * 1_000_000_000
- 判断:
scale > SECOND_SCALE
→ 放大 - 计算:
secRatio = HOUR_SCALE / SECOND_SCALE = 3600
- 结果:
1 * 3600 = 3600
(1小时 = 3600秒)
cvt方法
Java
private static long cvt(long d, long dst, long src) {
long r, m;
if (src == dst)
return d;
else if (src < dst)
return d / (dst / src);
else if (d > (m = Long.MAX_VALUE / (r = src / dst)))
return Long.MAX_VALUE;
else if (d < -m)
return Long.MIN_VALUE;
else
return d * r;
}
- 毫秒到秒(缩小):cvt(1000L, SECOND_SCALE, MILLI_SCALE)
- src = MILLI_SCALE = 1_000_000
- dst = SECOND_SCALE = 1_000_000_000
- 判断:src < dst → 缩小
- 计算:dst / src = 1_000_000_000 / 1_000_000 = 1000
- 结果:1000 / 1000 = 1(1000毫秒 = 1秒)
- 秒到秒(同单位):cvt(1L, SECOND_SCALE, SECOND_SCALE)
- src = SECOND_SCALE = 1_000_000_000
- dst = SECOND_SCALE = 1_000_000_000
- 判断:src == dst → 相等
- 结果:直接返回1(1秒 = 1秒)
- 分钟到秒(放大):cvt(2L, SECOND_SCALE, MINUTE_SCALE)
- src = MINUTE_SCALE = 60_000_000_000
- dst = SECOND_SCALE = 1_000_000_000
- 判断:src > dst → 放大
- 计算:r = src / dst = 60_000_000_000 / 1_000_000_000 = 60
- 结果:2 * 60 = 120(2分钟 = 120秒)
无论是toSeconds方法还是cvt方法,都是把TimeUnit
将复杂的时间单位转换抽象为数学运算,再通过简单的scale
比较去判断进行放大还是缩小操作,逻辑优雅且高效。
总结
Doug Lea大佬把TimeUnit写的非常精炼且明了,读下来也受益匪浅,业务赶多了,还是需要静下心来学习一下大佬的想法和写法,不只是把代码实现了,还能写的干净利落。