深度长文 | 循序渐进解读计算机中的时间—应用篇(下)

420 阅读7分钟
原文链接: mp.weixin.qq.com

本文字数1994

预计阅读时间:15分钟

3  对时间的存储

讲完表示再来看日期时间的存储方法。以MySQL数据库为例,介绍数据存储的方式,以及与Java程序的交互。

3.1

MySQL的日期时间类型介绍

将MySQL提供的几种日期时间数据结构列表如下:

    ●YEAR 类型用于表示年份,默认是4位,可以直接插入4位数字或字符串。由于YEAR类型占用空间很小,如果只想表示年份,并在其表示范围内,不失是一种很好的选择。

    ●DATE 类型用于表示日期,以 YYYY-MM-DD 格式显示。指“日历页上的日期”,没有时区概念,类似于 Java8 中的 LocalDate。

    ●TIME 类型用于表示时间,以 HH:MM:SS 格式显示,精度为秒。指“挂钟显示的时间”,没有时区概念,类似于 Java8 中的 LocalTime。

    ●DATETIME 类型是 DATE 和 TIME 的结合,占8位,它把日期和时间封装到格式为 “YYYYMMDDHHMMSS” 的整数中,可以记录较 TIMESTAMP 更长的时间。没有时区概念,类似于 Java8 中的 LocalDateTime。

    ●TIMESTAMP 类型也是表示日期加时间,但是表示的时间较短,和32位 Unix 时间戳相同。TIMESTAMP 类型表示的时间与时区有关,MySQL服务器、操作系统、客户端连接等都有时区设置,插入日期时会先转换为本地时区后再存放,查询日期时会将日期转换为本地时区后再显示。如果插入时没有指定 TIMESTAMP 列的值,则系统默认设置为 '0000-00-00 00:00:00',也可以手动设置为添加当前时间。

3.2

MySQL的日期时间类型比较与选择

YEAR、DATE、TIME 三种类型都功能不同,YEAR 存年份,DATE 存日期,TIME 存时间,按业务需求进行挑选即可。主要比较 DATETIME 和 TIMESTAMP 类型:

    ●时区属性不同:DATETIME 无时区属性,TIMESTAMP支持时区变换;

    ●表示范围不同:DATETIME 表示范围更大,为1000-01-01 00:00:00——9999-12-31 23:59:59,TIMESTAMP 只能表示32位Unix时间戳的范围;

    ●空间占用不同:TIMESTAMP 只要 4 bytes,效率更高。

    ●综上:若有明确的需要时区转换或不需要时区转换的问题,则根据业务需求选择对应的,否则会出现逻辑错误;else if 32位Unix时间戳的范围够用则推荐选择 TIMESTAMP 类型,因为空间效率更高。

还有一种可选项:每次涉及日期时间时全部用Unix时间戳表示,Java中用long,MySQL中用INT类型。

详见[如何正确地处理时间-廖雪峰]。好处是体现了“存储与显示分离”的原则,且易于比较。

但是肉眼无法快速识别时间戳确实带来了很大的麻烦,况且Java和MySQL开发出那么多类型就是为了方便使用(不然上文全都白讲了),也可以解决大多数问题,所以个人并不推荐这种做法(也可能是开发经验不够,没有理解到廖老师这个点的精髓)。

3.3

与Java的交互

笔者自己总结了Java 和 MySQL 日期时间数据类型的一种映射关系:

    ●在MySQL数据库创建表包含各种类型的字段用于测试:

DATETIME、TIMESTAMP类型默认精确到秒,如需毫秒或更高精度,需手动指定字段长度,如下:

 1CREATE TABLE `test_time` ( 2  `id` int(11) NOT NULL AUTO_INCREMENT, 3  `time1` date DEFAULT NULL, 4  `time2` time DEFAULT NULL, 5  `time3` year(4) DEFAULT NULL, 6  -- 长度为3精确到毫秒   7  `time4` datetime(3) DEFAULT NULL, 8  -- 长度为6精确到微秒   9  `time5` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,10  PRIMARY KEY (`id`)11) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;

    ●使用spring mybatis generator 插件创建 model,自动创建的数据格式都是 java.util.Date。将数据读出时,无关联时区的数据格式都会加上当前系统默认时区,缺少的数据会用缺省值填充。这是一种非常浪费且繁琐且容易出错的方式。如下图:

   

    ●MySQL 版本在5.1.37以上的,驱动在4.2以上的,可以使用Java8中的新类型,几乎可以说完美匹配。

4  时区转换的操作

需要时区转换的时间一定不是“挂钟上的时间”,而是时间轴上确定的一个“绝对时间”。所以时区转换分为两个方面:由被展示的字符串添加某时区信息后转为Java对象,或由固定时区的Java对象转换时区后展示。

下面各种方式实现这两个转换:

4.1

无脑加减操作

根据目标时区和原时区的时差直接加减,“硬核转换”,极不推荐。

4.2

Date+SimpleDateFormat

如下图(注意,转为Date对象的时候自动变为了系统时区):

或者更简单的利用“z”这个域:

4.3

ZonedDateTime + DateTimeFormatter

4.4

用时间戳处理

用各种方法得到该时间点的时间戳,然后转化为Java对象,添加时区信息,输出。

4.5

与MySQL的交互转换

按照上述MySQL与Java交互中所述,将MySQL存储的时间转换为Java对象,然后按照2,3方法转换即可。

5  总结

本篇文章全面贴近实际开发,首先从日常代码遇到的问题出发,介绍了一些常识和会遇到的问题。

随后介绍了Java中日期时间的获取、数据格式表示及格式转换方法。其中深入源码详细介绍了Java7中的日期时间数据结构,拆解了可能会遇到的线程安全问题及解决办法,并在使用层面介绍了Java8中日期时间新API及其优点,源码中的复杂计算方法有待今后研究。

接着在存储方面介绍了MySQL的日期时间类型及如何选择的建议,并给出了与Java各种日期时间类型的转换示例。最后根据时区转换的需求给出各种数据结构的时区转换操作方法。

本篇为上篇-应用篇,下篇中会详细解释一些底层日期时间的处理,如为什么不同操作系统获取当前时间的速度有数量级差异;高并发场景用 System.currenTimeMillis() 会出现什么问题及怎么解决;Linux中有哪些时间相关系统调用及他们的区别;系统对于类似 Thread.sleep(long millis) 的“时间段”长度是如何控制的;以上这些底层问题如何影响我们的程序设计等。

参考资料:

[1]https://www.liaoxuefeng.com/article/978494994163392

[2]https://docs.oracle.com/javase/8/docs/api/

[3]http://tutorials.jenkov.com/java-date-time/index.html

[4]https://www.iteye.com/blog/jinnianshilongnian-1876339

[5]https://www.jianshu.com/p/d9977a048dab

[6]《高性能MySQL - 4.1.4日期和时间类型》

也许你还想看

(▼点击文章标题或封面查看)

搜狐新闻推荐算法 | 呈现给你的,都是你所关心的

2018-08-30

新闻推荐系统的CTR预估模型

2019-04-18

互联网架构演进之路

2018-08-16

加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛

   看完啦?留言支持一下再走吧~~~

▼▼▼ 写留言