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写的非常精炼且明了,读下来也受益匪浅,业务赶多了,还是需要静下心来学习一下大佬的想法和写法,不只是把代码实现了,还能写的干净利落。

相关推荐
Sirius Wu1 小时前
Maven环境如何正确配置
java·maven
健康平安的活着2 小时前
java之 junit4单元测试Mockito的使用
java·开发语言·单元测试
Java小白程序员3 小时前
Spring Framework :IoC 容器的原理与实践
java·后端·spring
xuTao6673 小时前
Easy Rules 规则引擎详解
java·easy rules
m0_480502644 小时前
Rust 入门 KV存储HashMap (十七)
java·开发语言·rust
杨DaB4 小时前
【SpringBoot】Swagger 接口工具
java·spring boot·后端·restful·swagger
YA3334 小时前
java基础(九)sql基础及索引
java·开发语言·sql
桦说编程5 小时前
方法一定要有返回值 \ o /
java·后端·函数式编程
小李是个程序5 小时前
登录与登录校验:Web安全核心解析
java·spring·web安全·jwt·cookie
David爱编程5 小时前
Java 创建线程的4种姿势,哪种才是企业级项目的最佳实践?
java·后端