TimeUnit源码分享

很久没有写博客了,去年一整年都在忙一个重要的项目,现在回过头来有点时间,优化了一些业务流程、代码规范。在优化的时候就看到了很多"魔法数",项目里充斥着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 乘法 粗单位数值需倍增为细单位
  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写的非常精炼且明了,读下来也受益匪浅,业务赶多了,还是需要静下心来学习一下大佬的想法和写法,不只是把代码实现了,还能写的干净利落。

相关推荐
2301_780789666 小时前
跨平台适配!SDK 游戏盾兼容全终端游戏场景
java·网络·ddos
学长毕设7 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
步行cgn8 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川8 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵8 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
秋天的怀念_David8 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas1009 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
niucloud-admin9 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php
霸道流氓气质10 小时前
智能客服组件:分层意图识别、RAG Advisor 与人工转接状态机的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
captain37610 小时前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis