玩转 Java 时间 + 面试题

点击上方蓝色字关注咱们~





在 JDK 8 以前,Java 语言为咱们提供了两个类用于操做时间,它们分别是:java.util.Date 和 java.util.Calendar,但在 JDK 8 的时候为了解决旧时间操做类的一些缺陷,提供了几个新的类,用于操做时间和日期,它们分别是:LocalTime、LocalDateTime、Instant,都位于 java.time 包下。时间的操做在咱们平常的开发中常常见到,好比,业务数据都要记录建立时间和修改时间,并要把这些时间格式化以后显示到前端页面,再好比咱们须要计算业务数据的时间间隔等,都离不开对时间的操做,那如何正确而优雅地使用时间?这就是咱们接下来要讨论的话题。前端

01
时间基础知识科普

格林威治时间java

格林威治(又译格林尼治)是英国伦敦南郊原格林威治天文台的所在地,它是世界计算时间和地球经度的起点,国际经度会议 1884 年在美国华盛顿召开,会上经过协议,以通过格林威治天文台的经线为零度经线(即本初子午线),做为地球经度的起点,并以格林威治为“世界时区”的起点。web

格林威治时间和北京时间的关系面试

格林威治时间被定义为世界时间,就是 0 时区,北京是东八区。也就是说格林威治时间的 1 日 0 点,对应到北京的时间就是 1 日 8 点。sql

时间戳缓存

时间戳是指格林威治时间 1970-01-01 00:00:00(北京时间 1970-01-01 08:00:00)起至如今的总秒数。安全

02
JDK 8 以前的时间操做

1 获取时间微信

Date date = new Date();
System.out.println(date);
Calendar calendar = Calendar.getInstance();
Date time = calendar.getTime();
System.out.println(time);

2 获取时间戳

long ts = new Date().getTime();
System.out.println(ts);
long ts2 = System.currentTimeMillis();
System.out.println(ts2);
long ts3 = Calendar.getInstance().getTimeInMillis();
System.out.println(ts3);

3 格式化时间

SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sf.format(new Date()));  // output:2019-08-16 21:46:22

SimpleDateFormat 构造参数的含义,请参考如下表格信息:多线程

字符 含义 示例
y yyyy-1996
M MM-07
d 月中的天数 dd-02
D 年中的天数 121
E 星期几 星期四
H 小时数(0-23) HH-23
h 小时数(1-12) hh-11
m 分钟数 mm-02
s 秒数 ss-03
Z 时区 +0800


使用示例:架构

  • 获取星期几:new SimpleDateFormat("E").format(new Date())

  • 获取当前时区:new SimpleDateFormat("Z").format(new Date*())


注意事项:在多线程下 SimpleDateFormat 是非线程安全的,所以在使用 SimpleDateFormat 时要注意这个问题。在多线程下,若是使用不当,可能会形成结果不对或内存泄漏等问题。

4 时间转换

SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String 转 Date
String str = "2019-10-10 10:10:10";
System.out.println(sf.parse(str));
//时间戳的字符串 转 DateString ts
String = "1556788591462";
// import java.sql
Timestamp ts = new Timestamp(Long.parseLong(tsString)); // 时间戳的字符串转 Date
System.out.println(sf.format(ts));

注意事项:当使用 SimpleDateFormat.parse() 方法进行时间转换的时候,SimpleDateFormat 的构造函数必须和待转换字符串格式一致。

5 得到昨天此刻时间

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
System.out.println(calendar.getTime());

JDK 8 时间操做

JDK 8 对时间操做新增了三个类:LocalDateTime、LocalDate、LocalTime。

  • LocalDate 只包含日期,不包含时间,不可变类,且线程安全。

  • LocalTime 只包含时间,不包含日期,不可变类,且线程安全。

  • LocalDateTime 既包含了时间又包含了日期,不可变类,且线程安全。


03
线程安全性


值得一提的是 JDK 8 中新增的这三个时间相关的类,都是线程安全的,这极大地下降了多线程下代码开发的风险。

1 获取时间

// 获取日期
LocalDate localDate = LocalDate.now();
System.out.println(localDate);    // output:2019-08-16
// 获取时间
LocalTime localTime = LocalTime.now();
System.out.println(localTime);    // output:21:09:13.708
// 获取日期和时间
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);    // output:2019-08-16T21:09:13.708

2 获取时间戳

long milli = Instant.now().toEpochMilli(); // 获取当前时间戳(精确到毫秒)
long second = Instant.now().getEpochSecond(); // 获取当前时间戳(精确到秒)
System.out.println(milli);  // output:1565932435792
System.out.println(second); // output:1565932435

3 时间格式化

// 时间格式化①
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String timeFormat = dateTimeFormatter.format(LocalDateTime.now());
System.out.println(timeFormat);  // output:2019-08-16 21:15:43
// 时间格式化②
String timeFormat2 = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(timeFormat2);    // output:2019-08-16 21:17:48

4 时间转换

String timeStr = "2019-10-10 06:06:06";
LocalDateTime dateTime = LocalDateTime.parse(timeStr,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateTime);

5 得到昨天此刻时间

LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.plusDays(-1);
System.out.println(yesterday);

相关面试题

1. 获取当前时间有几种方式?

答:获取当前时间常见的方式有如下三种:

  • new Date()

  • Calendar.getInstance().getTime()

  • LocalDateTime.now()

2. 如何获取昨天此刻的时间?

答:如下为获取昨天此刻时间的两种方式:

// 获取昨天此刻的时间(JDK 8 之前)
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE,-1);
System.out.println(c.getTime());
// 获取昨天此刻的时间(JDK 8)
LocalDateTime todayTime = LocalDateTime.now();
System.out.println(todayTime.plusDays(-1));

3. 如何获取本月的最后一天?

答:如下为获取本月最后一天的两种方式:

// 获取本月的最后一天(JDK 8 之前)
Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
System.out.println(ca.getTime());
// 获取本月的最后一天(JDK 8)
LocalDate today = LocalDate.now();
System.out.println(today.with(TemporalAdjusters.lastDayOfMonth()));

4. 获取当前时间的时间戳有几种方式?

答:如下为获取当前时间戳的几种方式:

  • System.currentTimeMillis()

  • new Date().getTime()

  • Calendar.getInstance().getTime().getTime()

  • Instant.now().toEpochMilli()

  • LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()


其中,第四种和第五种方式是 JDK 8 才新加的。

5. 如何优雅地计算两个时间的相隔时间?

答:JDK 8 中可使用 Duration 类来优雅地计算两个时间的相隔时间,代码以下:

LocalDateTime dt1 = LocalDateTime.now();
LocalDateTime dt2 = dt1.plusSeconds(60);
Duration duration = Duration.between(dt1, dt2);
System.out.println(duration.getSeconds());  // output:60

6. 如何优雅地计算两个日期的相隔日期?

答:JDK 8 中可使用 Period 类来优雅地计算两个日期的相隔日期,代码以下:

LocalDate d1 = LocalDate.now();
LocalDate d2 = d1.plusDays(2);
Period period = Period.between(d1, d2);
System.out.println(period.getDays());   //output:2

7. SimpleDateFormat 是线程安全的吗?为何?

答:SimpleDateFormat 是非线程安全的。由于查看 SimpleDateFormat 的源码能够得知,全部的格式化和解析,都须要经过一个中间对象进行转换,这个中间对象就是 Calendar,这样的话就形成非线程安全。试想一下当咱们有多个线程操做同一个 Calendar 的时候后来的线程会覆盖先来线程的数据,那最后其实返回的是后来线程的数据,所以 SimpleDateFormat 就成为了非线程的了。

8. 怎么保证 SimpleDateFormat 的线程安全?

答:保证 SimpleDateFormat 线程安全的方式以下:

  • 使用 Synchronized,在须要时间格式化的操做使用 Synchronized 关键字进行包装,保证线程堵塞格式化;

  • 手动加锁,把须要格式化时间的代码,写到加锁部分,相对 Synchronized 来讲,编码效率更低,性能略好,代码风险较大(风险在于不要忘记在操做的最后,手动释放锁);

  • 使用 JDK 8 的 DateTimeFormatter 替代 SimpleDateFormat。

9. JDK 8 中新增的时间类都有哪些优势?

答:JDK 8 中的优势具体有如下几个优势,以下:

  • 线程安全性

  • 使用的便利性(如获取当前时间戳的便利性、增减日期的便利性等)

  • 编写代码更简单优雅,如当前时间的格式化:LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

10. 如何比较两个时间(Date)的大小?

答:时间比较有如下三种方式:

  • 获取两个时间的时间戳,获得两个 long 类型的变量,两个变量相减,经过结果的正负值来判断大小;

  • 经过 Date 自带的 before()、after()、equals() 等方法比较,代码示例 date1.before(date2);

  • 经过 compareTo() 方法比较,代码示例:date1.compareTo(date2),返回值 -1 表示前一个时间比后一个时间小,0 表示两个时间相等,1 表示前一个时间大于后一个时间。

总结

JDK 8 以前使用 java.util.Date 和 java.util.Calendar 来操做时间,它们有两个很明显的缺点,第一,非线程安全;第二,API 调用不方便。JDK 8 新增了几个时间操做类 java.time 包下的 LocalDateTime、LocalDate、LocalTime、Duration(计算相隔时间)、Period(计算相隔日期)和 DateTimeFormatter,提供了多线程下的线程安全和易用性,让咱们能够更好的操做时间。

往期精选

分布式数据复制技术,今天就教你真正分身术

数据分布方式之哈希与一致性哈希,我就是个神算子

分布式存储系统三要素,掌握这些就离成功不远了

想要设计一个好的分布式系统,必须搞定这个理论

分布式通讯技术之发布订阅,干货满满

分布式通讯技术之远程调用:RPC

消息队列Broker主从架构详细设计方案,这一篇就搞定主从架构

消息中间件路由中心你会设计吗,不会就来学学

消息队列消息延迟解决方案,跟着作就好了

秒杀系统每秒上万次下单请求,咱们该怎么去设计

【分布式技术】分布式系统调度架构之单体调度,非掌握不可

CDN加速技术,做为开发的咱们真的不须要懂吗?

烦人的缓存穿透问题,今天教就你如何去解决

分布式缓存高可用方案,咱们都是这么干的

天天百万交易的支付系统,生产环境该怎么设置JVM堆内存大小

你的成神之路我已替你铺好,没铺你来捶我

本文分享自微信公众号 - 架构师修炼(jiagouxiulian)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索