isToday的高性能实现

fast fast fast

场景

业务中经常需要判断传入的时间是不是今天,之前封装有一个方法

public static boolean isToday(long timeMillis) {
        LocalDate now = LocalDate.now();
        LocalDate fromDate = Instant.ofEpochMilli(timeMillis).atZone(ZoneId.systemDefault()).toLocalDate();
        return now.equals(fromDate);
}

基于LocalDate比较,具体思路是将时间戳转换成LocalDate,再基于LocalDate进行比较。由于LocalDate内部有很多隐藏逻辑,且每次调用这个方法都要创建两个LocalDate对象,这段代码只能说是正确但低效。

我们需要一种更高效的方式,经过一番思考和行动,得出了下面这个方式

基于时间范围比较

它有两个特点

  1. 基于时间范围判断
  2. 利用线程封闭避免竞态

实现利用了Guava的Range工具类,可以快速判断某个值是否在给定范围内

public class TimeUtil {
    public static final long MILLIS_ONE_DAY = 24 * 60 * 60 * 1000;
    /**
     * 今日时间范围,[今日开始时间戳,今日结束时间戳)
     */
    private static final ThreadLocal> todayTimeRangeCache =
            ThreadLocal.withInitial(TimeUtil::createTodayTimeRange);

    /**
     * 获取今日0点
     *
     * @return
     */
    public static long getTodayZeroMillis() {
        return LocalDateTime.of(LocalDate.now(), LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }
    
    /**
     * 传入时间是否属于今天
     *
     * @param timeMillis
     * @return
     */
    public static boolean isToday(long timeMillis) {
        long now = System.currentTimeMillis();
        Range todayTimeRange = todayTimeRangeCache.get();
        if (!todayTimeRange.contains(now)) {
            Range timeRange = createTodayTimeRange();
            todayTimeRangeCache.set(timeRange);
            todayTimeRange = timeRange;
        }
        return todayTimeRange.contains(timeMillis);
    }

    private static Range createTodayTimeRange() {
        long todayStartTime = getTodayZeroMillis();
        // 如果需要考虑夏令时, 这里需要改为其他方式,逻辑于getTodayZeroMillis类似
        long todayEndTime = todayStartTime + MILLIS_ONE_DAY;
        return Range.closedOpen(todayStartTime, todayEndTime);
    }
}

新旧方案对比

方式 基于LocalDate对比 基于时间范围对比
内存消耗 每次调用都要创建两个LocalDate对象,O(n) 每个线程仅需要创建一个,O(1)
性能 时间戳转换为对象,根据当前时间创建对象,均为复杂的逻辑 获取一次当前时间,两到三次比较操作

isToday是一个高频调用的方法,对一个长期运行的项目,经过优化后的方式很有意义。

你可能感兴趣的:(isToday的高性能实现)