TimeUnit源码分享

很久没有写博客了,去年一整年都在忙一个重要的项目,现在回过头来有点时间,优化了一些业务流程、代码规范。在优化的时候就看到了很多"魔法数",项目里充斥着60 * 1000L、300000、120000等时间"魔法数",虽然不会看不懂,但是阅读起来始终有点障碍,还容易搞错单位。于是就统一使用TimeUnit改了一下,点进去之后发现是"c/c++风格"Java代码。回顾一下大学时代的C语言时也一起学习一下Doug Lea大佬的编程思维。

一、TimeUnit概览

TimeUnitjava.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的核心转换方法(如toSecondstoMillis)通过比较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;
}

每个类型的TimeUnitscale表示其相对于纳秒的倍数,SECOND_SCALE1_000_000_000(1秒的纳秒数)。通过比较scaleSECOND_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 乘法 粗单位数值需倍增为细单位
  1. 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秒)
  1. TimeUnit.SECONDS.toSeconds(1)
  • scale = SECOND_SCALE = 1_000_000_000
  • 判断:scale == SECOND_SCALE → 相等
  • 结果:直接返回1
  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;
}
  1. 毫秒到秒(缩小):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秒)
  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秒)
  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写的非常精炼且明了,读下来也受益匪浅,业务赶多了,还是需要静下心来学习一下大佬的想法和写法,不只是把代码实现了,还能写的干净利落。

相关推荐
大模型玩家七七1 小时前
基于语义切分 vs 基于结构切分的实际差异
java·开发语言·数据库·安全·batch
寻星探路6 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
曹牧8 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7259 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎9 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄9 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿9 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds10 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化