1.什么是SimpleDateFormat
在java doc對SimpleDateFormat的解釋如下:
SimpleDateFormatis a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting(date → text), parsing (text → date), and normalization.
SimpleDateFormat是一個用來對位置敏感的格式化和解析日期的實體類令蛉。他允許把日期格式化成text舷暮,把text解析成日期和規(guī)范化。
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")); }復(fù)制代碼
1.首先需要定義一個日期的pattern,這里我們定義的是"yyyy-mm-dd HH:mm:ss" 黎茎,也就是我們這個simpleDateFormat不管是格式化還是解析都需要按照這個pattern。
2.對于format需要傳遞Date的對象当悔,會返回一個String類型傅瞻,這個String會按照我們上面的格式生成。
3.對于parse需要傳遞一個按照上面pattern的字符串盲憎,如果傳遞錯誤的pattern會拋出java.text.ParseException異常嗅骄,如果傳遞正確的會生成一個Date對象。
附:格式占位符 G 年代標(biāo)志符 y 年 M 月 d 日 h 時 在上午或下午 (1~12) H 時 在一天中 (0~23) m 分 s 秒 S 毫秒 E 星期 D 一年中的第幾天 F 一月中第幾個星期幾 w 一年中第幾個星期 W 一月中第幾個星期 a 上午 / 下午 標(biāo)記符 k 時 在一天中 (1~24) K 時 在上午或下午 (0~11) z 時區(qū)復(fù)制代碼
2.SimpleDateFormat的隱患
很多初學(xué)者焙畔,或者一些經(jīng)驗比較淺的java開發(fā)工程師掸读,用SimpleDateFormat會出現(xiàn)一些奇奇怪怪的BUG。
1.結(jié)果值不對:轉(zhuǎn)換的結(jié)果值經(jīng)常會出人意料,和預(yù)期不同儿惫,往往讓很多人摸不著頭腦澡罚。
2.內(nèi)存泄漏: 由于轉(zhuǎn)換的結(jié)果值不對,后續(xù)的一些操作肾请,如一個循環(huán)留搔,累加一天處理一個東西,但是生成的日期如果異常導(dǎo)致很大的話铛铁,會讓這個循環(huán)變成一個類似死循環(huán)一樣導(dǎo)致系統(tǒng)內(nèi)存泄漏隔显,頻繁觸發(fā)GC,造成系統(tǒng)不可用饵逐。
為什么會出現(xiàn)這么多問題呢括眠?因為SimpleDateFormat線程不安全,很多人都會寫個Util類倍权,然后把SimpleDateFormat定義成全局的一個常量掷豺,所有線程都共享這個常量:
protected static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");public static Date formatDate(String date) throws ParseException {returndayFormat.parse(date);}復(fù)制代碼
為什么SimpleDateFormat會線程不安全呢,在SimpleDateFormat源碼中,所有的格式化和解析都需要通過一個中間對象進(jìn)行轉(zhuǎn)換薄声,那就是Calendar当船,而這個也是我們出現(xiàn)線程不安全的罪魁禍?zhǔn)祝囅胍幌庐?dāng)我們有多個線程操作同一個Calendar的時候后來的線程會覆蓋先來線程的數(shù)據(jù)默辨,那最后其實返回的是后來線程的數(shù)據(jù)德频,這樣就導(dǎo)致我們上面所述的BUG的產(chǎn)生:
/在這里順便給大家推薦一個架構(gòu)交流群:617434785,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring缩幸,MyBatis壹置,Netty源碼分析,高并發(fā)桌粉、高性能蒸绩、分布式、微服務(wù)架構(gòu)的原理铃肯,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系患亿。還能領(lǐng)取免費的學(xué)習(xí)資源。相信對于已經(jīng)工作和遇到技術(shù)瓶頸的碼友押逼,在這個群里會有你需要的內(nèi)容步藕。
/ 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) {caseTAG\_QUOTE\_ASCII_CHAR: toAppendTo.append((char)count);break;?caseTAG\_QUOTE\_CHARS: toAppendTo.append(compiledPattern, i, count); i += count;break;? default: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break; } }returntoAppendTo; }復(fù)制代碼
3.如何避坑
對于SimpleDateFormat的解決方法有下面幾種:
3.1新建SimpleDateFormat
上面出現(xiàn)Bug的原因是因為所有線程都共用一個SimpleDateFormat,這里有個比較好解決的辦法挑格,每次使用的時候都創(chuàng)建一個新的SimpleDateFormat,我們可以在DateUtils中將創(chuàng)建SimpleDateFormat放在方法內(nèi)部:
public static Date formatDate(String date) throws ParseException { SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");returndayFormat.parse(date);}復(fù)制代碼
上面這個方法雖然能解決我們的問題但是引入了另外一個問題就是咙冗,如果這個方法使用量比較大,有可能會頻繁造成Young gc漂彤,整個系統(tǒng)還是會受一定的影響雾消。
3.2使用ThreadLocal
使用ThreadLocal能避免上面頻繁的造成Young gc灾搏,我們對每個線程都使用ThreadLocal進(jìn)行保存,由于ThreadLocal是線程之間隔離開的立润,所以不會出現(xiàn)線程安全問題:
private static ThreadLocal simpleDateFormatThreadLocal = new ThreadLocal<>(); public static Date formatDate(String date) throws ParseException { SimpleDateFormat dayFormat = getSimpleDateFormat();returndayFormat.parse(date); }? private static SimpleDateFormatgetSimpleDateFormat() { SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();if(simpleDateFormat == null){ simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd? HH:mm:ss") simpleDateFormatThreadLocal.set(simpleDateFormat); }returnsimpleDateFormat; }復(fù)制代碼
3.3使用第三方工具包
雖然上面的ThreadLocal能解決我們出現(xiàn)的問題狂窑,但是第三方工具包提供的功能更加強大,在java中有兩個類庫比較出名一個是Joda-Time,一個是Apache common包
3.3.1 Joda-Time(推薦)
Joda-Time 令時間和日期值變得易于管理桑腮、操作和理解泉哈。對于我們復(fù)雜的操作都可以使用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()));復(fù)制代碼
但是在我們的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");復(fù)制代碼
3.3.2 common-lang包
在common-lang包中有個類叫FastDateFormat烫沙,由于common-lang這個包基本被很多Java項目都會引用,所以你可以不用專門去引用處理時間包隙笆,即可處理時間斧吐,在FastDateFormat中每次處理時間的時候會創(chuàng)建一個calendar,使用方法比較簡單代碼如下所示:
FastDateFormat.getInstance().format(new Date());
3.4升級jdk8(推薦)
在java8中Date這個類中的很多方法包括構(gòu)造方法都被打上了@Deprecated廢棄的注解,取而代之的是LocalDateTime,LocalDate LocalTime這三個類:
LocalDate無法包含時間仲器;
LocalTime無法包含日期;
LocalDateTime才能同時包含日期和時間仰冠。
如果你是Java8乏冀,那你一定要使用他,在日期的格式化和解析方面不用考慮線程安全性洋只,代碼如下:
public static String formatTime(LocalDateTime time,String pattern) {returntime.format(DateTimeFormatter.ofPattern(pattern)); }復(fù)制代碼
?
當(dāng)然localDateTime是java8的一大亮點辆沦,當(dāng)然不僅僅只是解決了線程安全的問題,同樣也提供了一些其他的運算比如加減天數(shù):
//日期加上一個數(shù),根據(jù)field不同加不同值,field為ChronoUnit.* public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {returntime.plus(number, field); }? //日期減去一個數(shù),根據(jù)field不同減不同值,field參數(shù)為ChronoUnit.* public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field){returntime.minus(number,field); }復(fù)制代碼
最后识虚,如果你擔(dān)心使用LocalDateTime 會對你現(xiàn)有的代碼產(chǎn)生很大的改變的話肢扯,那你可以將他們兩進(jìn)行互轉(zhuǎn):
//Date轉(zhuǎn)換為LocalDateTime public static LocalDateTime convertDateToLDT(Date date) {returnLocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); }? //LocalDateTime轉(zhuǎn)換為Date public static Date convertLDTToDate(LocalDateTime time) {returnDate.from(time.atZone(ZoneId.systemDefault()).toInstant()); }