Java之SimpleDateFormat的用法详解

这篇具有很好参考价值的文章主要介绍了Java之SimpleDateFormat的用法详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。

最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。

在阿里巴巴Java开发手册中,有如下明确规定:

那么,本文就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。

SimpleDateFormat用法

SimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。

在Java中,可以使用SimpleDateFormat的format方法,将一个Date类型转化成String类型,并且可以指定输出格式。

// Date转String
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr = sdf.format(data);
System.out.println(dataStr);

以上代码,转换的结果是:2018-11-25 13:00:00,日期和时间格式由"日期和时间模式"字符串指定。如果你想要转换成其他格式,只要指定不同的时间模式就行了。

在Java中,可以使用SimpleDateFormat的parse方法,将一个String类型转化成Date类型。

// String转Data
System.out.println(sdf.parse(dataStr));
日期和时间模式表达方法

在使用SimpleDateFormat的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表如下:

![-w717][1]

模式字母通常是重复的,其数量确定其精确表示。如下表是常用的输出格式的表示方法。

![-w535][2]

输出不同时区的时间

时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。

世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。

现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。

由于不同的时区的时间是不一样的,甚至同一个国家的不同城市时间都可能不一样,所以,在Java中想要获取时间的时候,要重点关注一下时区问题。

默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,这也是为什么我们通过只要使用new Date()就可以获取中国的当前时间的原因。

那么,如何在Java代码中获取不同时区的时间呢?SimpleDateFormat可以实现这个功能。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(Calendar.getInstance().getTime()));

以上代码,转换的结果是: 2018-11-24 21:00:00 。既中国的时间是11月25日的13点,而美国洛杉矶时间比中国北京时间慢了16个小时(这还和冬夏令时有关系,就不详细展开了)。

如果你感兴趣,你还可以尝试打印一下美国纽约时间(America/New_York)。纽约时间是2018-11-25 00:00:00。纽约时间比中国北京时间早了13个小时。

当然,这不是显示其他时区的唯一方法,不过本文主要为了介绍SimpleDateFormat,其他方法暂不介绍了。

SimpleDateFormat线程安全性

由于SimpleDateFormat比较常用,而且在一般情况下,一个应用中的时间显示模式都是一样的,所以很多人愿意使用如下方式定义SimpleDateFormat:

public class Main {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
    }
}

这种定义方式,存在很大的安全隐患。

问题重现

我们来看一段代码,以下代码使用线程池来执行时间输出。

   /** * @author Hollis */ 
   public class Main {

    /**
     * 定义一个全局的SimpleDateFormat
     */
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 使用ThreadFactoryBuilder定义一个线程池
     */
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    /**
     * 定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
     */
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                    //时间增加
                    calendar.add(Calendar.DATE, finalI);
                    //通过simpleDateFormat把时间转换成字符串
                    String dateString = simpleDateFormat.format(calendar.getTime());
                    //把字符串放入Set中
                    dates.add(dateString);
                    //countDown
                    countDownLatch.countDown();
            });
        }
        //阻塞,直到countDown数量为0
        countDownLatch.await();
        //输出去重后的时间个数
        System.out.println(dates.size());
    }
}

以上代码,其实比较简单,很容易理解。就是循环一百次,每次循环的时候都在当前时间基础上增加一个天数(这个天数随着循环次数而变化),然后把所有日期放入一个线程安全的带有去重功能的Set中,然后输出Set中元素个数。

上面的例子我特意写的稍微复杂了一些,不过我几乎都加了注释。这里面涉及到了[线程池的创建][3]、[CountDownLatch][4]、lambda表达式、线程安全的HashSet等知识。感兴趣的朋友可以逐一了解一下。

正常情况下,以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字。

原因就是因为SimpleDateFormat作为一个非线程安全的类,被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。

在阿里巴巴Java开发手册的第一章第六节——并发处理中关于这一点也有明确说明:

那么,接下来我们就来看下到底是为什么,以及该如何解决。

线程不安全原因

通过以上代码,我们发现了在并发场景中使用SimpleDateFormat会有线程安全问题。其实,JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

那么接下来分析下为什么会出现这种问题,SimpleDateFormat底层到底是怎么实现的?

我们跟一下SimpleDateFormat类中format方法的实现其实就能发现端倪。

![][5]

SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。

由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。

假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11,还没等执行完,线程2又执行了calendar.setTime把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的calendar.getTime得到的时间就是线程2改过之后的。

除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。

所以,不要把SimpleDateFormat作为一个共享变量使用。

如何解决

前面介绍过了SimpleDateFormat存在的问题以及问题存在的原因,那么有什么办法解决这种问题呢?

解决方法有很多,这里介绍三个比较常用的方法。

使用局部变量

for (int i = 0; i < 100; i++) {
    //获取当前时间
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        // SimpleDateFormat声明成局部变量
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //时间增加
        calendar.add(Calendar.DATE, finalI);
        //通过simpleDateFormat把时间转换成字符串
        String dateString = simpleDateFormat.format(calendar.getTime());
        //把字符串放入Set中
        dates.add(dateString);
        //countDown
        countDownLatch.countDown();
    });
}

SimpleDateFormat变成了局部变量,就不会被多个线程同时访问到了,就避免了线程安全问题。

加同步锁

除了改成局部变量以外,还有一种方法大家可能比较熟悉的,就是对于共享变量进行加锁。

for (int i = 0; i < 100; i++) {
    //获取当前时间
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        //加锁
        synchronized (simpleDateFormat) {
            //时间增加
            calendar.add(Calendar.DATE, finalI);
            //通过simpleDateFormat把时间转换成字符串
            String dateString = simpleDateFormat.format(calendar.getTime());
            //把字符串放入Set中
            dates.add(dateString);
            //countDown
            countDownLatch.countDown();
        }
    });
}

通过加锁,使多个线程排队顺序执行。避免了并发导致的线程安全问题。

其实以上代码还有可以改进的地方,就是可以把锁的粒度再设置的小一点,可以只对simpleDateFormat.format这一行加锁,这样效率更高一些。

使用ThreadLocal

第三种方式,就是使用 ThreadLocal。 ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。

/**
 * 使用ThreadLocal定义一个全局的SimpleDateFormat
 */
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

用 ThreadLocal 来实现其实是有点类似于缓存的思路,每个线程都有一个独享的对象,避免了频繁创建对象,也避免了多线程的竞争。

当然,以上代码也有改进空间,就是,其实SimpleDateFormat的创建过程可以改为延迟加载。这里就不详细介绍了。

使用DateTimeFormatter

如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。就像官方文档中说的,这个类 simple beautiful strong immutable thread-safe。

//解析日期
String dateStr= "2016年10月25日";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date= LocalDate.parse(dateStr, formatter);

//日期转换为字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
String nowStr = now .format(format);
System.out.println(nowStr);

总结

本文介绍了SimpleDateFormat的用法,SimpleDateFormat主要可以在String和Date之间做转换,还可以将时间转换成不同时区输出。同时提到在并发场景中SimpleDateFormat是不能保证线程安全的,需要开发者自己来保证其安全性。

主要的几个手段有改为局部变量、使用synchronized加锁、使用Threadlocal为每一个线程单独创建一个等。

希望通过此文,你可以在使用SimpleDateFormat的时候更加得心应手。文章来源地址https://www.toymoban.com/news/detail-849444.html

到了这里,关于Java之SimpleDateFormat的用法详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java SimpleDateFormat设置时区导致时间不正确的解决方案

    问题介绍 在Android开发中经常遇到有的接口需要上传当前时间,如果后台要求直接传一个long类型的时间戳还好,因为这个时间戳是跟时区无关的,如果后台接口要求传的是格式化的时间,若本地设备设置的时区与后台要就的时区不一致,就会导致上传的时间不准确。 不完善的

    2024年02月11日
    浏览(45)
  • Java程序设计入门教程--日期格式化类SimpleDateFormat

           在程序设计中,经常用到特定的日期格式,此时就可以使用 java.text 包中的 SimpleDateFormat 类来对日期时间进行格式化,如可以将日期转换为指定格式的文本,也可将文本转换为日期。 目标格式 使用SimpleDateFormat类时,首先要定义一个要转换的日期时间目标格式。目标格

    2024年02月07日
    浏览(45)
  • Java面试题:SimpleDateFormat是线程安全的吗?使用时应该注意什么?

    在Java开发中,我们经常需要获取和处理时间,这需要使用到各种不同的方法。其中,使用SimpleDateFormat类来格式化时间是一种常见的方法。虽然这个类看上去功能比较简单,但是如果使用不当,也可能会引发一些问题。 首先我们要明确一点, SimpleDateFormat不是线程安全的 。

    2024年04月26日
    浏览(39)
  • 想让你的代码简洁,试试这个SimpleDateFormat类高深用法

    本文分享自华为云社区《从入门到精通:SimpleDateFormat类高深用法,让你的代码更简洁!》,作者:bug菌。 @[toc] 日期时间在开发中是非常常见的需求,尤其是在处理与时间相关的业务逻辑时,我们需要对日期时间进行格式化、比较等操作。在Java中,我们可以使用 SimpleDateFor

    2024年02月08日
    浏览(42)
  • Java中日期时间格式化方法SimpleDateFormat和DateTimeFormatter使用完整示例及区别说明

    示例代码: 示例截图:  这里完整的用两种方法分别实现了日期和String的来回转换,鉴于SimpleDateFormat早已过时,且非线程安全,所以推荐大家首选使用DateTimeFormatter,用法基本都是差不多的。变化不大。但是DateTimeFormatter需要Java Level 8(8 - Lambdas, type annotations etc.),需留意。

    2023年04月09日
    浏览(43)
  • 【JAVA语言-第12话】API中的工具类 之 Date,DateFormat,SimpleDateFormat,Calendar类的详细解析

    目录 日期和时间 1.1 Date类 1.1.1 概述 1.1.2 常用方法  1.1.3 案例 1.2 DateFormat类  1.2.1 概述 1.2.2 常用方法 1.3 SimpleDateFormat类 1.3.1 概述 1.3.2 构造方法 1.3.3 模式字符  1.3.4 日期转字符串 1.3.5 字符串转日期 1.4 Calendar类 1.4.1 概述 1.4.2 静态方法 1.4.3 常用成员方法 1.4.4 成员方法中参数

    2024年02月02日
    浏览(42)
  • SimpleDateFormat 线程安全问题修复方案

    在日常的开发过程中,我们不可避免地会使用到 JDK8 之前的 Date 类,在格式化日期或解析日期时就需要用到 SimpleDateFormat 类,但由于该类并不是线程安全的,所以我们常发现对该类的不恰当使用会导致日期解析异常,从而影响线上服务可用率。 以下是对 SimpleDateFormat 类不恰当

    2024年02月12日
    浏览(41)
  • 日期与时间【Date/SimpleDateFormat/Calendar】

    视频链接:https://www.bilibili.com/video/BV1Cv411372m?p=121vd_source=9140dcc493e34a9f4e95ca2f8f71bbd3 Date类的对象在java中代表的是当前所在系统的此刻日期时间。 Date的构造器 public Date():创建一个Date对象,代表的是系统当前此刻日期时间。 Date的常用方法 public long getTime():获取时间对象的毫秒值

    2024年02月03日
    浏览(46)
  • SimpleDateFormat为什么是线程不安全的?

    大家好,我是哪吒。 在日常开发中,Date工具类使用频率相对较高,大家通常都会这样写: 这很简单啊,有什么争议吗? 你应该听过“时区”这个名词,大家也都知道,相同时刻不同时区的时间是不一样的。 因此在使用时间时,一定要给出时区信息。 对于当前的上海时区和

    2024年02月20日
    浏览(53)
  • SimpleDateFormat :{ ParseException: Unparseable date} 问题原因以及解决方法

    SimpleDateFormat simpleFormat = new SimpleDateFormat(“yyyy-MM-dd hh:mm:ss”); 我所使用的与实际要转换的不一致,导致报错 在转换的时候必须保持 转换字符串和转换类型格式一致 提供一个代码片段(只是简单做了一下判断,只能满足几种日期转换( 写的不是很好,有待优化 )) 参考此篇

    2024年02月14日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包