在java doc对SimpleDateFormat的解释以下:java
SimpleDateFormat
is a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting
(date → text), parsing (text → date), and normalization.api
SimpleDateFormat是一个用来对位置敏感的格式化和解析日期的实体类。他容许把日期格式化成text,把text解析成日期和规范化。安全
simpleDateFormat的使用方法比较简单:app
public static void main(String[] args) throws Exception { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); System.out.println(simpleDateFormat.format(new Date())); System.out.println(simpleDateFormat.parse("2018-07-09 11:10:21")); }
1.首先须要定义一个日期的pattern,这里咱们定义的是"yyyy-mm-dd HH:mm:ss" ,也就是咱们这个simpleDateFormat不论是格式化仍是解析都须要按照这个pattern。工具
2.对于format须要传递Date的对象,会返回一个String类型,这个String会按照咱们上面的格式生成。spa
3.对于parse须要传递一个按照上面pattern的字符串,若是传递错误的pattern会抛出java.text.ParseException异常,若是传递正确的会生成一个Date对象。线程
附:格式占位符 G 年代标志符 y 年 M 月 d 日 h 时 在上午或下午 (1~12) H 时 在一天中 (0~23) m 分 s 秒 S 毫秒 E 星期 D 一年中的第几天 F 一月中第几个星期几 w 一年中第几个星期 W 一月中第几个星期 a 上午 / 下午 标记符 k 时 在一天中 (1~24) K 时 在上午或下午 (0~11) z 时区
不少初学者,或者一些经验比较浅的java开发工程师,用SimpleDateFormat会出现一些奇奇怪怪的BUG。code
1.结果值不对:转换的结果值常常会出人意料,和预期不一样,每每让不少人摸不着头脑。orm
2.内存泄漏: 因为转换的结果值不对,后续的一些操做,如一个循环,累加一天处理一个东西,可是生成的日期若是异常致使很大的话,会让这个循环变成一个相似死循环同样致使系统内存泄漏,频繁触发GC,形成系统不可用。对象
为何会出现这么多问题呢?由于SimpleDateFormat线程不安全,不少人都会写个Util类,而后把SimpleDateFormat定义成全局的一个常量,全部线程都共享这个常量:
protected static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd"); public static Date formatDate(String date) throws ParseException { return dayFormat.parse(date); }
为何SimpleDateFormat会线程不安全呢,在SimpleDateFormat源码中,全部的格式化和解析都须要经过一个中间对象进行转换,那就是Calendar,而这个也是咱们出现线程不安全的罪魁祸首,试想一下当咱们有多个线程操做同一个Calendar的时候后来的线程会覆盖先来线程的数据,那最后其实返回的是后来线程的数据,这样就致使咱们上面所述的BUG的产生:
/
/ 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; }
对于SimpleDateFormat的解决方法有下面几种:
上面出现Bug的缘由是由于全部线程都共用一个SimpleDateFormat,这里有个比较好解决的办法,每次使用的时候都建立一个新的SimpleDateFormat,咱们能够在DateUtils中将建立SimpleDateFormat放在方法内部:
public static Date formatDate(String date) throws ParseException { SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd"); return dayFormat.parse(date); }
上面这个方法虽然能解决咱们的问题可是引入了另一个问题就是,若是这个方法使用量比较大,有可能会频繁形成Young gc,整个系统仍是会受必定的影响。
使用ThreadLocal能避免上面频繁的形成Young gc,咱们对每一个线程都使用ThreadLocal进行保存,因为ThreadLocal是线程之间隔离开的,因此不会出现线程安全问题:
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>(); public static Date formatDate(String date) throws ParseException { SimpleDateFormat dayFormat = getSimpleDateFormat(); return dayFormat.parse(date); } private static SimpleDateFormat getSimpleDateFormat() { SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get(); if (simpleDateFormat == null){ simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss") simpleDateFormatThreadLocal.set(simpleDateFormat); } return simpleDateFormat; }
虽然上面的ThreadLocal能解决咱们出现的问题,可是第三方工具包提供的功能更增强大,在java中有两个类库比较出名一个是Joda-Time,一个是Apache common包
Joda-Time 令时间和日期值变得易于管理、操做和理解。对于咱们复杂的操做均可以使用Joda-Time操做,下面我列举两个例子,对于把日期加上90天,若是使用原生的Jdk咱们须要这样写:
Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); SimpleDateFormat sdf = new SimpleDateFormat("E MM/dd/yyyy HH:mm:ss.SSS"); calendar.add(Calendar.DAY\_OF\_MONTH, 90); System.out.println(sdf.format(calendar.getTime()));
可是在咱们的joda-time中只须要两句话,而且api也比较通俗易懂,因此你为何不用Joda-Time呢?
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS");
在common-lang包中有个类叫FastDateFormat,因为common-lang这个包基本被不少Java项目都会引用,因此你能够不用专门去引用处理时间包,便可处理时间,在FastDateFormat中每次处理时间的时候会建立一个calendar,使用方法比较简单代码以下所示:
FastDateFormat.getInstance().format(new Date());
在java8中Date这个类中的不少方法包括构造方法都被打上了@Deprecated废弃的注解,取而代之的是LocalDateTime,LocalDate LocalTime这三个类:
若是你是Java8,那你必定要使用他,在日期的格式化和解析方面不用考虑线程安全性,代码以下:
public static String formatTime(LocalDateTime time,String pattern) { return time.format(DateTimeFormatter.ofPattern(pattern)); }
固然localDateTime是java8的一大亮点,固然不只仅只是解决了线程安全的问题,一样也提供了一些其余的运算好比加减天数:
//日期加上一个数,根据field不一样加不一样值,field为ChronoUnit.* public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) { return time.plus(number, field); } //日期减去一个数,根据field不一样减不一样值,field参数为ChronoUnit.* public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field){ return time.minus(number,field); }
最后,若是你担忧使用LocalDateTime 会对你现有的代码产生很大的改变的话,那你能够将他们两进行互转:
//Date转换为LocalDateTime public static LocalDateTime convertDateToLDT(Date date) { return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); } //LocalDateTime转换为Date public static Date convertLDTToDate(LocalDateTime time) { return Date.from(time.atZone(ZoneId.systemDefault()).toInstant()); }
若是你喜欢这篇文章欢迎点赞,转发。能够扫描公众号便可得到个人1v1 VIP服务。