可能大部分同学都听说过一个知名的故事。一位小学老师,为了让同学们停止吵闹,给出了一道数据题 1+2+3+…+100 = ? 原本以为可以让他们安静二三十分钟,结果1分钟不到,就有一个小朋友举手回答了出来,老师漫不经心的看了一眼答案,万万没想到竟然是正确的。
这个小朋友只有9岁,他就是高斯。
高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210
后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?
高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记上标注着:5343,因此可算出那天是:1791年12月15日。高斯获得博士学位的那天日记上标着:8113 。现在请算出高斯获得博士学位的年月日?
我们来抽象一下问题,本质上是要求取某年某月某日后的n天,是哪年哪月哪日?
方案一:循环遍历法
比较容易想到的就是遍历的方式,循环n次,就可以推导出n天后的日期了,我们来尝试一下。
public static void calc(int year, int month, int day, int n) { for (int i = 0; i < n; i++) {
// 日子一天天过 day++;
// 如果过到月底,需要把日期重置,月份+1 if(day > getMonthLastDay(year,month)){ day = 1; month++;
// 如果过到年底,需要把月份和日期同时重置,年份+1 if(month >12){
month = 1; day = 1; year++; } } } System.out.println( n + "天后的日期为:"+year + "-" + month + "-" +day); }
先把已知条件带入,在main函数中执行 calc(1777,4,30,5343);
可以得到结果为1791年12月16日,和题目上的1791年12月15日相差一天,这是什么原因呢?
细想可以察觉,高斯生日的这一天其实被算作了第一天的,也就是我们带入方法时,需要传入1777年4月29日
calc(1777,4,29,5343); 的运行结果是 1791年12月15日
而 calc(1777,4,29,8113); 的运行结果是 1799年7月16日,可以想见高斯在22岁时就已获得博士学位了,真正的年少得志。
方案二:善用工具法
jdk8给我们提供了一系列很友好的日期api,让我们求解此类问题时,可以快速拿到答案。
// 创建一个高斯生日前一天的日期
LocalDate date = LocalDate.of(1777,4,29);
// 调用增加天数的方法,能够直接获得n天后的日期 System.out.println(date.plusDays(8113));
既然有我们自己来书写的方案,还有jdk提供的方案,那么问题来了,哪种方案执行更快、效率更好呢?
long start = System.currentTimeMillis();
calc(1777, 4, 29, 8113);
long cost = System.currentTimeMillis() - start;
System.out.println("耗时" + cost);
System.out.println("===========");
start = System.currentTimeMillis();
//日期工具 jdk8 joda time
LocalDate date = LocalDate.of(1777, 4, 29);
System.out.println(date.plusDays(8113));
cost = System.currentTimeMillis() - start;
System.out.println("耗时" + cost);
我们通过毫秒计算工具来测试一下,得到结果如下
注意,这里的时间单位是ms,两种方案实际相差为0.1s左右。
大跌你的眼镜吧,我们自己写的实现竟然比jdk提供的实现方式快,这是为什么呢?
我们深入LocalDate类的实现看一下,和我们自己实现的有何区别?
public LocalDate plusDays(long daysToAdd) { if (daysToAdd == 0) { return this; }
// addExact就是一个简单的加法运算
// toEpochDay() 其中epoch代表的是元年,计算机元年是1970年1月1日,这里计算的是当前日期距离元年的天数
// 当前日期距离元年的天数 + 当前日期过后的n天 = n天后距离元年的天数 long mjDay = Math.addExact(toEpochDay(), daysToAdd);
// ofEpochDay(int n) 是计算距离元年n天的日期 return LocalDate.ofEpochDay(mjDay); }
再点击进 toEpochDay() 看一下
public long toEpochDay() { long y = year; long m = month; long total = 0; total += 365 * y; if (y >= 0) { total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400; } else { total -= y / -4 - y / -100 + y / -400; } total += ((367 * m - 362) / 12); total += day - 1; if (m > 2) { total--; if (isLeapYear() == false) { total--; } } return total - DAYS_0000_TO_1970; }
可以看到,这个方法都是通过公式来计算的,而 ofEpochDay() 也同样如此,所以我们可以简单把工具法等价为公式法。
为什么循环法比公式法还快呢,我们拉长一下这个问题。当要计算更多天数之后的日期时,两者的表现如何呢?
我们计算 8113333 天后的日期,可以发现的循环法耗时和公式法耗时基本一致,都在0.1s左右。再增加到81133333 天后呢,可以明显的看到公式法仍然保持在0.1s,而循环法的耗时变为1s以上,大幅提高。
这说明在计算次数增加之后,循环法的耗时会成斜线增长,而公式法的耗时基本保持在水平直线上,这也是jdk的设计者们所做的权衡。对我们自身来讲,不同的应用场景下,可以选择不同的实现方式,没有最好的,只有最适合的,你get到了吗?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ri-ji/760.html