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 来确保线程安全性。

相关推荐
阿伟*rui2 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj4 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck4 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei4 小时前
java的类加载机制的学习
java·学习
码农小旋风5 小时前
详解K8S--声明式API
后端
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml45 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~6 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616886 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7896 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot