首页 > 编程语言 >java老鸟教你如何高效优雅的进行时间格式化

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

时间:2022-12-07 20:05:14浏览次数:38  
标签:格式化 format SimpleDateFormat 线程 LocalDateTime Date java 老鸟


前言

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


一、常见时间格式化方式

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方式。


标签:格式化,format,SimpleDateFormat,线程,LocalDateTime,Date,java,老鸟
From: https://blog.51cto.com/u_15905482/5919992

相关文章

  • Java中的Runnable、Callable、Future、FutureTask的区别与示例
    Java中存在Runnable、Callable、Future、FutureTask这几个与线程相关的类或者接口,在Java中也是比较重要的几个概念,我们通过下面的简单示例来了解一下它们的作用于区别。Ru......
  • Java难点 | StringBuilder类/StringBuffer类
    StringBuilder类/StringBuffer类stringBuffer/strinaBuilder可以看做可变长度字符串。stringBuffer/stringBuilder初始化容量16.stringBuffer/stringBuilder是完成字符......
  • 初识Java
    ​Java有什么好处?它具有令人赏心悦目的语法和易于理解的语义。在企业级系统、Web开发、Android开发、大数据开发都起到了十分重要的作用Java的祖师爷既然要学Java,那就有必......
  • 【Java】【Mybatis】如何调用存储过程和存储函数
    https://www.jb51.net/article/230756.htmMybatis调用存储过程MyBatis支持使用存储过程的配置。当使用存储过程时,需要设置一个参数“mode”,其值有IN(输入参数)、OUT(输出参......
  • Java基础语法
    1.注释​ 注释是对代码的解释和说明文字。Java中的注释分为三种:单行注释://这是单行注释文字多行注释:/*这是多行注释文字这是多行注释文字这是多行注释文字......
  • JavaWeb三大组件(Servlet、Filter、Listener)
    前言JavaWeb三大组件指的是:Servlet程序、Filter过滤器、Listener监听器,它们在JavaWeb开发中分别提供不同的功能,然而很多人有只用过Servlet、Filter,很少接触到Listener......
  • Java题目集6~8总结Blog
    一、前言题目集六:知识点:Java基本语法,对类的继承、抽象以及多态和容器的应用。题量:较少。难度:难。题目集七:知识点:Java基本语法,类的设计、继承和容器及其容器中排序;迭......
  • JavaScript(三)
    ❤️‍JavaScriptjQuery查找标签jQuery节点操作jQuery事件绑定bootstrap页面框架❤️‍jQuery查找标签......
  • java面试(多线程)
    1. Callable、Future、FutureTash详解Callable与Future是在JAVA的后续版本中引入进来的,Callable类似于Runnable接口,实现Callable接口的类与实现Runnable的类都是可以被线程......
  • java面试(基础)
    0.面向对象1.1封装:利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。减少耦合,提高可重用性。1.2继承:is-a Cat可以当做Animal来使......