![](https://img.haomeiwen.com/i2400535/4ee67aed410dfbd2.png)
场景
业务中经常需要判断传入的时间是不是今天,之前封装有一个方法
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对象,这段代码只能说是正确但低效。
我们需要一种更高效的方式,经过一番思考和行动,得出了下面这个方式
基于时间范围比较
它有两个特点
- 基于时间范围判断
- 利用线程封闭避免竞态
实现利用了Guava的Range工具类,可以快速判断某个值是否在给定范围内
public class TimeUtil {
public static final long MILLIS_ONE_DAY = 24 * 60 * 60 * 1000;
/**
* 今日时间范围,[今日开始时间戳,今日结束时间戳)
*/
private static final ThreadLocal<Range<Long>> 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<Long> todayTimeRange = todayTimeRangeCache.get();
if (!todayTimeRange.contains(now)) {
Range<Long> timeRange = createTodayTimeRange();
todayTimeRangeCache.set(timeRange);
todayTimeRange = timeRange;
}
return todayTimeRange.contains(timeMillis);
}
private static Range<Long> createTodayTimeRange() {
long todayStartTime = getTodayZeroMillis();
// 如果需要考虑夏令时, 这里需要改为其他方式,逻辑于getTodayZeroMillis类似
long todayEndTime = todayStartTime + MILLIS_ONE_DAY;
return Range.closedOpen(todayStartTime, todayEndTime);
}
}
新旧方案对比
方式 | 基于LocalDate对比 | 基于时间范围对比 |
---|---|---|
内存消耗 | 每次调用都要创建两个LocalDate对象,O(n) | 每个线程仅需要创建一个,O(1) |
性能 | 时间戳转换为对象,根据当前时间创建对象,均为复杂的逻辑 | 获取一次当前时间,两到三次比较操作 |
isToday是一个高频调用的方法,对一个长期运行的项目,经过优化后的方式很有意义。
网友评论