在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服務。