1.什么是SimpleDateFormat

在java doc对SimpleDateFormat的解释如下:

SimpleDateFormat是用于以对语言环境敏感的形式格式化和解析日期的具体类。它容许格式化(日期→文本),解析(文本→日期)和规范化。

SimpleDateFormat是一个用来对地位敏感的格式化和解析日期的实体类。他容许把日期格式化成文本,把文本解析成日期和规范化。

1.1应用SimpleDateFormat

simpleDateFormat的应用办法比较简单:
`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.首先须要定义一个日期的模式,这里咱们定义的是“ yyyy-mm-dd HH:mm:ss”,也就是咱们这个simpleDateFormat不论是格式化还是解析都须要依照这个模式。

2.对于format须要传递Date的对象,会返回一个String类型,这个String会遵循咱们下面的格局生成。

3.对于parse须要传递一个遵循上述模式的款式,如果传递谬误的模式会引发java.text.ParseException异样,如果传递正确的会生成一个日期对象。
`附:格局占位符

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 时区
`

2.SimpleDateFormat的隐患

很多初学者,或者一些一些教训比拟浅的java开发工程师,用SimpleDateFormat会呈现一些奇奇怪怪的BUG。

1.后果值不对:转换的后果值常常会出乎意料,和预期不同,常常让很多人摸不着头脑。

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,而这个也是咱们呈现线程不平安的罪魁祸首,试想一下当咱们有多一个线程操作同一个日历,过后的线程会笼罩先来线程的数据,那最初实际上返回的是起初线程的数据,这样就导致咱们下面所述的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;

}
`

3.如何避坑

对于SimpleDateFormat的解决办法有以下几种:

3.1新建SimpleDateFormat

下面呈现谬误的起因是因为所有线程都共享一个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,整个零碎还是会受肯定的影响。

3.2应用ThreadLocal

应用ThreadLocal能防止下面的从新造成的年老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;

}
`
3.3应用第三方工具包

尽管下面的ThreadLocal能解决咱们呈现的问题,然而第三方工具包提供的性能更弱小,在java中有两个类库比拟闻名一个是Joda-Time,一个是Apache common包

3.3.1举荐(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");`
3.3.2 common-lang包

在common-lang包中有个类叫FastDateFormat,因为common-lang这个包根本被很多Java我的项目都会援用,所以你能够不必专门去援用解决工夫包,即可解决工夫,在FastDateFormat中每次解决工夫的过后会创立一个日历,应用办法比较简单代码如下所示:
`FastDateFormat.getInstance()。format(new Date());
`
3.4降级jdk8(举荐)

在java8中Date这个类中的很多办法包含构造方法都被打上了@不倡议应用废除的注解,取而代之的是LocalDateTime,LocalDate LocalTime这三个类:

LocalDate无奈蕴含工夫;

LocalTime无奈蕴含日期;

LocalDateTime能同时蕴含日期和工夫。

如果你是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());

}
`