使用 Apache 的 FastDateFormat 代替 JDK 的 SimpleDateFormat,提升性能

SimpleDateFormat 的问题

在 Java 中,格式化日期通常使用 SimpleDateFormat 这个类。

我们知道,SimpleDateFormat 是线程不安全的,主要原因是 format 方法内部调用 calendar.setTime 方法,整个过程都是没有加锁或同步的,如果同时有多个线程调用到这一步,则会出现线程安全问题。

1
2
3
4
5
6
7
8
9
10
public final String format(Date date) {
return format(date, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString();
}

// 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);
...
}

所以,大部分时候则是在方法内部 new 出新的 DateFormat 对象再做格式化,如:DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

但在高访问量的情况下,频繁创建实例也会导致内存开销大和 GC 频繁问题。

FastDateFormat

Apache 的 commons-lang 包下有个 FastDateFormat 可以方便的解决上述问题。

查看其源码:

1
2
3
4
5
6
7
8
9
10
11
// 2.6 版本源码
public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
if (format == null) {
format = emptyFormat;
format.init(); // convert shell format into usable one
cInstanceCache.put(format, format); // this is OK!
}
return format;
}

FastDateFormat 通过 getInstance 静态方法获取对象,内部加了一个 cInstanceCache 缓存。当使用同样的 pattern 格式化时会命中缓存,返回缓存中的对象,避免重复创建实例。

拿到对象调用 format 方法,可以看出 Calender 是在 format 方法中创建的,所以不会出现 setTime 的线程安全问题。

1
2
3
4
5
public String format(Date date) {
Calendar c = new GregorianCalendar(mTimeZone, mLocale);
c.setTime(date);
return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
}

而且 getInstance 这个方法是加锁的,以保证获取实例的线程安全性。在新的版本(3.0 以上)中 getInstance 方法内更加使用 ConcurrentMap 做缓存提高并发性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 3.0 版本源码
abstract class FormatCache<F extends Format> {
static final int NONE = -1;
private final ConcurrentMap<MultipartKey, F> cInstanceCache;
private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache;
...
}

private static final FormatCache<FastDateFormat> cache = new FormatCache() {
protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) {
return new FastDateFormat(pattern, timeZone, locale);
}
};

public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
return (FastDateFormat)cache.getInstance(pattern, timeZone, locale);
}

DateFormatUtils

基于上面 FastDateFormat 类,commons-lang 包还提供了 DateFormatUtils 工具类,提供了两方面的功能:

  1. 提供静态的 format 方法,实现一行代码格式化日期。
  2. 内部初始化了一些 final 的 FastDateFormat,方便开发者直接调用。