深度长文 | 循序渐进解读计算机中的时间—应用篇(下)
本文字数: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
2019-04-18
2018-08-16
加入搜狐技术作者天团
千元稿费等你来!
戳这里!☛
看完啦?留言支持一下再走吧~~~
▼▼▼