代码编织梦想

前言

在日常项目开发过程中,相信大家一定都经常遇到时间格式化的场景。很多人可能都感觉非常简单,但是你的时间格式化方法真的优雅高效吗?


一、常见时间格式化方式

    public static void main(String[] args) {
        Date now = new Date(); // 创建一个Date对象,获取当前时间
        String strDateFormat = "yyyy-MM-dd HH:mm:ss";

        //新人菜鸟实现
        SimpleDateFormat f = new SimpleDateFormat(strDateFormat);
        System.out.println("SimpleDateFormat:" + f.format(now)); // 将当前时间袼式化为指定的格式

        //java8进阶实现
        LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        String result =  localDateTime.format(DateTimeFormatter.ofPattern(strDateFormat));
        System.out.println("DateTimeFormatter:"+result);

        //common-lang3老鸟实现
        result  = DateFormatUtils.format(now,strDateFormat);
        System.out.println("DateFormatUtils:"+result);

    }

二、分析

方式一:新人菜鸟实现

很多新人喜欢采用SimpleDateFormat进行时间格式化,这也是java8之前,jdk默认提供的时间格式化实现方式,它最大的问题是非线程安全的。

原因:
在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

/**
 * SimpleDateFormat线程安全测试
 */
public class SimpleDateFormatTest {
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));


    public void test() {
        while (true) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    String dateString = simpleDateFormat.format(new Date());
                    try {
                        Date parseDate = simpleDateFormat.parse(dateString);
                        String dateString2 = simpleDateFormat.format(parseDate);
                        System.out.println(dateString.equals(dateString2));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

输出:

true
false
true
true
false

出现了false,说明线程不安全

format方法源码分析:

protected Calendar calendar;

 // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }

可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。

解决方案:
  1、将SimpleDateFormat定义成局部变量
  2、 加一把线程同步锁:synchronized(lock)
  3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。

方式二:java8进阶实现

java8后,推荐使用DateTimeFormatter代替SimpleDateFormat。
DateTimeFormatter是线程安全的,默认提供了很多格式化方法,也可以通过ofPattern方法创建自定义格式化方法。

        //java8进阶实现
        LocalDateTime localDateTime = LocalDateTime.now();
        String result =  localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("DateTimeFormatter:"+result);

需要注意的是,java8的时候格式化都是针对LocalDate和LocalDateTime对象,其中LocalDateTime包含时分秒,而LocalDate只包含年月日,没有时分秒信息。

Java8 日期时间API,新增了LocalDate、LocalDateTime、LocalTime等线程安全类:

LocalDate:只有日期,诸如:2019-07-13
LocalTime:只有时间,诸如:08:30
LocalDateTime:日期+时间,诸如:2019-07-13 08:30

由于java8的时候格式化API都是针对LocalDate和LocalDateTime对象,所以date对象和LocalDate、LocalDateTime对象的相互转化就非常重要。

1、Date转换成LocalDate

public static LocalDate date2LocalDate(Date date) {
    if(null == date) {
        return null;
    }
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}

2、Date转换成LocalDateTime

public static LocalDateTime date2LocalDateTime(Date date) {
    if(null == date) {
        return null;
    }
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}

3、LocalDate转换成Date

public static Date localDate2Date(LocalDate localDate) {
    if(null == localDate) {
        return null;
    }
    ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
    return Date.from(zonedDateTime.toInstant());
}

4、LocalDateTime转换成Date

   public static Date localDateTime2Date(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

方式三:common-lang3老鸟实现

相信大家在项目开发过程中都多少使用过common-lang3中的一些API,都非常简单实用。在时间格式化方面,也给我们提供了非常好用的工具类DateFormatUtils。
这也是我最推荐的方式。

需要在项目中引入common-lang3的依赖。

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>

DateFormatUtils使用示例:

Date now = new Date(); // 创建一个Date对象,获取当前时间
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
String result  = DateFormatUtils.format(now,strDateFormat);
System.out.println("DateFormatUtils:"+result);

好处:
1、线程安全
2、简单高效
3、占用更小的内存

DateFormatUtils.format的内部实现中,是通过FastDateFormat进行时间格式化,而且对FastDateFormat对象进行了缓存处理,保证相同模式的格式化类型下不用重复生成FastDateFormat对象。

FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);

推荐使用方式:

/**
 * @Description   时间格式化工具类
 * @Date 2021/5/26 11:40 上午
 * @Version 1.0
 * @Copyright 2019-2021
 */
public class DateUtils extends DateFormatUtils {
      //继承DateFormatUtils

      //自定义的时间相关方法
}

总结

1、介绍了java时间格式化的3种常用方式。
2、SimpleDateFormat时间格式化主要的问题是非线程安全,多线程情况下会出现问题,通过跟踪源码说明了SimpleDateFormat非线程安全的原因,并提供了相应的解决方案。
3、介绍了java8下推荐的采用DateTimeFormatter进行时间格式化的使用方式,并提供了date到LocalDateTime、LocalDate的转换方式。
4、采用common-lang3中的DateFormatUtils实现时间格式化是我最推荐的,线程安全、API简单高效、占用内存低。并推荐了通过继承DateFormatUtils对象封装自己的时间工具类DateUtils方式。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/w1014074794/article/details/117112128

java基础-高效利用simpledateformat实现日期的转换_秋天该很好的博客-爱代码爱编程

《一:步骤》 1.得到SimpleDateFormat对象: SimpleDateFormat sdf = new SimpleDateFormat(); 2.申请日期转换模式: sdf.applyPattern

多线程安全时间格式化操作_飒行的博客-爱代码爱编程_多线程日期格式化

SimpleDateFormat是一个非线程安全的实现。从以下代码可以体现。   1.  package org.saxing;   2.     3.  import java.text.ParseException;   4.  import java.text.SimpleDateFormat;   5.  import java.uti

【战雷系列】再见!simpledateformat_li563868273的博客-爱代码爱编程

1.什么是SimpleDateFormat 在java doc对SimpleDateFormat的解释如下: SimpleDateFormat is a concrete class for formatting and

commons-lang(time应用)_格物致理,的博客-爱代码爱编程

JAVA的时间日期处理一直是一个比较复杂的问题,可以说大多数程序员都不能得心应手的处理这些问题,就算通过commons.lang.time包简化了,使用起来仍然有些麻烦,个人认为这可能是最复杂的一个子包。不过相对原始的JDK API,还是简化了不少,因此此子包也相当值得关注。 首先讨论一下关于时间的类,从 JDK 1.1 开始,Date的作用很有限,相应

简单高效的java日期处理工具类 -- 日期时间加减,大小比较_化猿和尚的博客-爱代码爱编程_org.joda.time.datetime 比较大小

package com.game.admin.util; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.text.ParseExce

java日期格式化比较好用工具类整合_memory丶of的博客-爱代码爱编程

在我们日常开发中必然会用到Date这个类,所以对于日期的格式化都有自己的理解 ,下面我分享一下我常用的DateUtils 许多你细化的方法和技巧。 日期格式化的格式有哪些 public static final Str

java时间简单格式化_巴拉巴拉朵的博客-爱代码爱编程

Java时间简单格式化 Java有一个非常好用的时间库Joda,大家用了都会说Joda大法好,但是今天也简单介绍下Java自带的时间处理模块,有时候需要快速添加Java日志打印出来,又是临时的需求,所以要快速实现出来

java格式化时间_easyhood的博客-爱代码爱编程

public static void main(String[] args) { // System.out.println(System.currentTimeMillis()); SimpleDateForma

时间格式化simpledateformat和fastdateformat_古城的风cll的博客-爱代码爱编程_fastdateformat

时间格式化SimpleDateFormat和FastDateFormat SimpleDateFormat和FastDateFormat主要都是对时间的格式化 FastDateFormat是线程安全的,可以直接使用,不必

使用Java优雅地获取当前时间的几种方式-爱代码爱编程

java 获取当前时间 1.通过Util包中的Date获取,使用SimpleDateFormat 格式化2.通过Util包中的Date获取,使用String.format()格式化3.通过Util包的Calendar获取4.通过Util包的Calendar获取,分别获取年月日时分秒 1.通过Util包中的Date获取,使用SimpleDateF

SimpleDateFormat、FastDateFormat和Joda-Time-爱代码爱编程

注意:SimpleDateFormat是线程不安全的,在多线程情况下会出现线程安全问题。而FastDateFormat和Joda-Time都是线程安全的,可以放心使用。例如:SimpleDateFormat在对时间进行格式化的方法format中,会先对calendar对象进行setTime的赋值,若是有多个线程同时操作一个SimpleDateFormat实

Java 8优雅处理时间和日期-爱代码爱编程

目录   一 基本操作 二 日常应用 2.1  Instant 与 Date 互转 2.2 LocalDateTime 与 Date 互转 2.3 日期时间格式化输出 2.4 两个日期或者时间进行比较 2.5 加法操作和减法操作 用过 java.util.Date 和 java.sql.Date 的或多或少都会觉得这这两个日期 API 包

java 日期识别_JAVA常规字符串日期格式自动识别-爱代码爱编程

JAVA常规字符串日期格式自动识别 /** * Test.java */ package com.dc.eurekaserver; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.regex.Pattern; /**

java 时间格式化 注解_Java关于时间格式化的方法-爱代码爱编程

目录 一般从数据库获取的时间或日期时间格式化为date或者datetime,为了方便前端渲染,API接口返回的时候需要对日期进行格式化转换,通常会用到 SimpleDateFormat 工具处理。 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); String ti