可能大部分同窗都据说过一个知名的故事。一位小学老师,为了让同窗们中止吵闹,给出了一道数据题 1+2+3+…+100 = ? 本来觉得可让他们安静二三十分钟,结果1分钟不到,就有一个小朋友举手回答了出来,老师漫不经心的看了一眼答案,万万没想到居然是正确的。api
这个小朋友只有9岁,他就是高斯。函数
高斯有个好习惯:不管如何都要记日记。他的日记有个不同凡响的地方,他从不注明年月日,而是用一个整数代替,好比:4210工具
后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光能够用于浪费呢?源码分析
高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记上标注着:5343,所以可算出那天是:1791年12月15日。高斯得到博士学位的那天日记上标着:8113 。如今请算出高斯得到博士学位的年月日?测试
咱们来抽象一下问题,本质上是要求取某年某月某往后的n天,是哪年哪月哪日?this
方案一:循环遍历法spa
比较容易想到的就是遍历的方式,循环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); code
能够获得结果为1791年12月16日,和题目上的1791年12月15日相差一天,这是什么缘由呢?blog
细想能够察觉,高斯生日的这一天其实被算做了第一天的,也就是咱们带入方法时,须要传入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到了吗?