Java中的SimpleDateFormat 指南

1. 简介

大家好,我是老谭说架构,今天要跟大家探讨下和深入了解 SimpleDateFormat

我们将了解简单的实例 和格式化样式 以及该类为处理语言环境和时区而公开的有用方法。

Java 的 SimpleDateFormat 类不是线程安全的,这是因为它的内部状态(例如,日期格式的模式字符串)是可变的。在一个多线程环境中,多个线程同时访问同一个 SimpleDateFormat 实例可能会导致数据竞争,从而产生不可预期的结果。

2. 简单实例

首先,让我们看看如何实例化一个新的 SimpleDateFormat 对象。

4 个可能的构造函数- 但为了与名称保持一致,让我们保持简单。我们开始所需的只是一个 表示我们想要的 日期模式 字符串

让我们从破折号分隔的日期模式开始,如下所示:

js 复制代码
"dd-MM-yyyy"

这将正确格式化日期,从当前日期开始,再从当前月份开始,最后从当前年份开始。我们可以使用简单的单元测试来测试我们的新格式化程序。我们将实例化一个新的 SimpleDateFormat 对象,并传入一个已知日期:

java 复制代码
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
assertEquals("24-05-1977", formatter.format(new Date(233345223232L)));

在上面的代码中,格式化程序将 毫秒 转换 为人类可读的日期 - 1977 年 5 月 24 日。

2.1. 工厂方法

尽管SimpleDateFormat 是一个可以快速构建日期格式化程序的便捷类,但我们还是鼓励使用DateFormat类上的工厂方法 getDateFormat()getDateTimeFormat()getTimeFormat()

使用这些工厂方法时,上面的例子看起来有点不同:

java 复制代码
DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT);
assertEquals("5/24/77", formatter.format(new Date(233345223232L)));

从上面我们可以看出,格式化选项的数量由DateFormat上的字段**预先确定。这在很大程度上 限制了我们可用的格式化选项, 这就是为什么我们在本文中坚持使用 SimpleDateFormat 。

2.2. 线程安全

SimpleDateFormat 的 JavaDoc明确指出

日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问某个格式,则必须进行外部同步。

问题原因:

  • SimpleDateFormat 对象包含一个 Calendar 实例,用于存储日期和时间信息。
  • SimpleDateFormat 对象被多个线程同时使用时,它们可能会修改 Calendar 实例的内部状态,从而导致日期和时间格式不一致。

所以 SimpleDateFormat 实例不是线程安全的,我们应该在并发环境中谨慎使用它们。

解决这个问题的最佳方法是将它们与ThreadLocal结合使用。这样,每个线程最终都有自己的 SimpleDateFormat 实例,并且缺乏共享使得程序线程安全:

java 复制代码
private final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal
  .withInitial(() -> new SimpleDateFormat("dd-MM-yyyy"));

使用 ThreadLocalSimpleDateFormat 对象存储在 ThreadLocal 中,这样每个线程都会拥有自己的 SimpleDateFormat 实例:

java 复制代码
private static ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

withInitial 方法的参数 是SimpleDateFormat 实例的供应商 。每次 ThreadLocal 需要创建实例时,它都会使用这个供应商。

然后我们可以通过ThreadLocal实例使用格式化程序:

java 复制代码
formatter.get().format(date)

ThreadLocal.get() 方法 首先为当前线程初始化SimpleDateFormat,然后重用该实例。

我们将这种技术称为线程限制,因为我们将每个实例的使用限制在一个特定的线程中。

还有另外两种方法可以解决同一问题:

  • 使用 同步 块或 ReentrantLock
  • 按需创建SimpleDateFormat的废弃实例

这两种方法都不推荐:前者在争用较高时会对性能造成很大影响,而后者会创建大量对象,给垃圾收集带来压力。

值得一提的是,从 Java 8 开始,引入了一个新的DateTimeFormatter类。新的DateTimeFormatter类是不可变的和线程安全的。如果我们使用 Java 8 或更高版本,建议使用新的*DateTimeFormatter类。

3. 解析日期

SimpleDateFormatDateFormat 不仅允许我们格式化日期 - 而且我们还可以反转操作。使用 parse 方法,我们可以输入 日期的 字符串 表示形式并返回等效的 Date 对象:

java 复制代码
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
Date myDate = new Date(233276400000L);
Date parsedDate = formatter.parse("24-05-1977");
assertEquals(myDate.getTime(), parsedDate.getTime());复制

这里需要注意的是,构造函数中提供的模式应该与使用 解析方法 解析的日期格式相同

4.日期时间模式

SimpleDateFormat 在格式化日期时提供了大量不同的选项。虽然完整列表可在JavaDocs中找到,但让我们探索一些更常用的选项:

日期组件返回的输出也在很大程度上取决于字符串使用的字符数。例如,我们以六月为例。如果我们将日期字符串定义为:**

arduino 复制代码
"MM"复制

那么我们的结果将显示为数字代码 - 06。但是,如果我们在日期字符串中添加另一个 M:

arduino 复制代码
"MMM"复制

然后我们得到的格式化日期显示为单词Jun

5. 应用区域设置

SimpleDateFormat 类还支持多种语言环境, 这些 语言环境是在调用构造函数时设置的。****

让我们通过用法语格式化日期来实践一下。我们将实例化一个 SimpleDateFormat 对象,同时将 Locale.FRANCE 提供给构造函数。

java 复制代码
SimpleDateFormat franceDateFormatter = new SimpleDateFormat("EEEEE dd-MMMMMMM-yyyy", Locale.FRANCE);
Date myFriday = new Date(1539341312904L);
assertTrue(franceDateFormatter.format(myFriday).startsWith("vendredi"));复制

通过提供给定日期(星期三下午),我们可以断言我们的 franceDateFormatter 已正确格式化日期。新日期正确地以 Vendredi 开头- 法语中的星期五!

值得注意的是构造函数的 Locale 版本中的一个小问题 -虽然支持许多语言环境,但不能保证完全覆盖。Oracle 建议在 DateFormat 类上使用工厂方法 来确保语言环境覆盖。

6. 改变时区

由于 SimpleDateFormat 扩展了 DateFormat 类,我们还可以使用setTimeZone 方法 来操作时区。让我们来看看实际操作:

java 复制代码
Date now = new Date();

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEE dd-MMM-yy HH:mm:ssZ");

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));
logger.info(simpleDateFormat.format(now));

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
logger.info(simpleDateFormat.format(now));复制

在上面的例子中,我们 在同一个SimpleDateFormat 对象上为两个不同的时区提供了相同的日期 。我们还在模式 字符串 的末尾 添加了"Z"字符以指示时区差异 。然后为用户记录格式 方法的输出 。

点击运行,我们可以看到相对于两个时区的当前时间:

java 复制代码
INFO: Friday 12-Oct-18 12:46:14+0100
INFO: Friday 12-Oct-18 07:46:14-0400

7. 总结

我们深入探讨了 SimpleDateFormat 的复杂性和它的线程非安全性问题。研究了如何实例化 SimpleDateFormat 以及模式 字符串 如何影响日期格式在最终尝试使用时区 之前,我们尝试过改变输出字符串的语言环境 。在多线程环境中,最好避免使用 SimpleDateFormat 对象,而是选择其他线程安全的替代方案,例如 DateTimeFormatter 或使用 ThreadLocal 来确保线程安全性。

相关推荐
猎人everest1 小时前
SpringBoot应用开发入门
java·spring boot·后端
山猪打不过家猪3 小时前
ASP.NET Core Clean Architecture
java·数据库·asp.net
AllowM3 小时前
【LeetCode Hot100】除自身以外数组的乘积|左右乘积列表,Java实现!图解+代码,小白也能秒懂!
java·算法·leetcode
不会Hello World的小苗3 小时前
Java——列表(List)
java·python·list
二十七剑4 小时前
jvm中各个参数的理解
java·jvm
东阳马生架构6 小时前
JUC并发—9.并发安全集合四
java·juc并发·并发安全的集合
计算机小白一个6 小时前
蓝桥杯 Java B 组之岛屿数量、二叉树路径和(区分DFS与回溯)
java·数据结构·算法·蓝桥杯
孤雪心殇6 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
菠菠萝宝6 小时前
【Java八股文】10-数据结构与算法面试篇
java·开发语言·面试·红黑树·跳表·排序·lru
不会Hello World的小苗6 小时前
Java——链表(LinkedList)
java·开发语言·链表