前言

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


一、常见时间格式化方式

    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() {@Overridepublic 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 FieldDelegateprivate StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {// Convert input date to time field listcalendar.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方式。

java老鸟教你如何高效优雅的进行时间格式化相关推荐

  1. 作为Java开发工程师,如何高效优雅地编写接口文档

    作为一名优秀的Java开发工程师,编写接口文档向来是一件很头疼的事情.本来就被bug纠缠的很累了,你还让我干这? 其实,你可以试试ApiPost. ApiPost的定位是Postman+Swagger ...

  2. 如何编写高效优雅 Java 程序

    文章目录 编写高效优雅 Java 程序 面向对象 01.构造器参数太多怎么办? Builder 模式: 02.不需要实例化的类应该构造器私有 03.不要创建不必要的对象 04.避免使用终结方法 05. ...

  3. 教你快速高效接入SDK——SDK接入抽象层的设计

    小黑终于有了自己的家:http://www.uustory.com/,欢迎来坐坐. 题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市 ...

  4. 如何写出和阿里大佬一样高效优雅的打码

    导读 明代王阳明先生在<传习录>谈为学之道时说: 私欲日生,如地上尘,一日不扫,便又有一层.着实用功,便见道无终穷,愈探愈深,必使精白无一毫不彻方可. 代码中的"坏味道" ...

  5. 手把手教你如何高效落地单项目管理 | 一看既会

    手把手教你如何高效落地单项目管理,云效Projex是新一代企业级研发协作平台,集成了敏捷研发项目管理的最佳实践,提供了针对项目.迭代.需求.缺陷等多个维度的协同管理以及相关的统计报告,让研发团队高效协 ...

  6. 教你快速高效接入SDK——手游聚合SDK框架中渠道SDK部分的接入(就是实现抽象层的接口而已)

    U8SDK技术博客:http://www.uustory.com/,欢迎来坐坐. 百度传课已经停运,最新U8SDK视频教程已经转移至B站:U8SDK最新视频教程 题记:很多做游戏开发的人,估计都或多或 ...

  7. 6年Java老鸟聊聊新人到底要不要学Java,从事互联网《打工人的那些事》

    6年Java老鸟聊聊新人到底要不要学Java,从事互联网<打工人的那些事> 文章目录 为什么有本文 一些客观事实 优势 劣势 点题 数据来源参考地址 大家为什么选择互联网编程这条路,估摸着 ...

  8. 从9个组件开始,教你如何高效的阅读nginx源码?

    从9个组件开始,教你如何高效的阅读nginx源码?|内存池.线程池.内存共享组件实现. http处理流程.phase原理.红黑树.配置文件.惊群.原子操作 专注于服务器后台开发,包括C/C++,Lin ...

  9. 【附源码】计算机毕业设计JAVA早教课程管理系统

    [附源码]计算机毕业设计JAVA早教课程管理系统 目运行 环境项配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(Inte ...

最新文章

  1. NSURL 基本方法 absoluteString
  2. 不同系统下的shell的不同_方向盘越来越重:转向系统不同 原因也不同
  3. python for selenium 数据驱动测试
  4. PHPUnit简介及使用(thinkphp5的单元测试安装及使用)
  5. 乐在其中设计模式(C#) - 命令模式(Command Pattern)
  6. 【语义分割】全卷积网络(Fully Convolutional Networks, FCN)详解
  7. 关于def __init__():
  8. 配置Https 和 HSTS
  9. 这样的书 我改怎么起名呢?
  10. Lucene.NET入门实例
  11. log4cpp 使用完全手册
  12. 杨忠愍斗严嵩,为国为民,名垂千古
  13. GDScript:关于派生类调用基类方法的一个注意事项
  14. 鬼谷八荒逆天改命修改教程(3月亲测有效)
  15. AntV G6将节点修改成图片
  16. 字符串的输入输出处理
  17. 形参和实参的储存单元是否一致?
  18. uIP各部分协议代码的分析
  19. 前端案例 ——注册页面(html+css实现)
  20. 阿里云盘——多电脑实时同步文件的实现

热门文章

  1. 同步和异步、阻塞和非阻塞之间的关系以及同步阻塞、同步非阻塞、异步阻塞、异步非阻塞的含义
  2. 如何修改网页的页面内容
  3. “标杆”突围:企业的学习能力、速度及意愿
  4. SSM的全部jar包免费百度云下载
  5. MTK Android User版本实现 root 功能 可进行APK的删减 和 系统img的替换
  6. Python爬虫实战 | 抓取小说网完结小说斗罗大陆
  7. python网上批量下载表格_python爬虫智能翻页批量下载文件的实例详解
  8. 填写的姓名或身份证与公众号主体信息不一致
  9. java开发用amd处理器_谈谈AMD CPU购机心得 与 写代码的感受
  10. 零基础自学SQL课程 | DELETE 删除语句