万能的SimpleDateFormat可以把java.util.Date对象, 或者类似 "2010-11-24 23:23:11.666"的
字符串转换成我们需要的格式或者时间对象。
但是由于时间的概念复杂,又牵扯到时区与本地化,导致了SimpleDateFormat需要处理太多的时间细节,
new一个SimpleDateFormat需要华为太多的时间,这样可能会想到缓存SimpleDateFormat对象
但是万能的SimpleDateFormat恰恰又不是现成安全的。
如果在单线程情况下,缓存SimpleDateFormat对象是不错的选择。
package com.haitao.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class SimpleDateFormatUtils {
public static final String DATE_PARTEN = "yyyy-MM-dd HH:mm:ss.SSS";
// 静态化缓存
private static SimpleDateFormat format = new SimpleDateFormat(DATE_PARTEN);
public static Date cachedParseDate(String str) {
try {
return format.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static String cachedFormatDate(Date date) {
return format.format(date);
}
public static Date parseDate(String str) {
SimpleDateFormat tempFormat = new SimpleDateFormat(DATE_PARTEN);
try {
return tempFormat.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static String formatDate(Date date) {
SimpleDateFormat tempFormat = new SimpleDateFormat(DATE_PARTEN);
return tempFormat.format(date);
}
}
在本机上测试, 10W次 字符串->Date与 10W次 Date> 字符串:
package com.haitao.test;
import java.util.Date;
import com.haitao.utils.SimpleDateFormatUtils;
public class SimpleDateFormatTest {
private static int COUNT = 0;
/**
* 生成日期字符串数据
*/
public static String[] genrateDateStr(int count) {
String[] array = new String[count];
for(int i = 0; i < count; i++) {
array[i] = SimpleDateFormatUtils.cachedFormatDate(new Date());
}
return array;
}
/**
* 生成日期数据
*/
public static Date[] genrateDate(int count) {
String[] strArray = genrateDateStr(count);
Date[] dateArray = new Date[count];
for(int i = 0; i < count; i++) {
dateArray[i] = SimpleDateFormatUtils.cachedParseDate(strArray[i]);
}
return dateArray;
}
/**
* 缓存SimpleDateFormat对象, 转换String->Date
*/
public void cachedParseDateTest(String dateStr) {
long start = System.currentTimeMillis();
for(int i = 0; i < COUNT; i++) {
SimpleDateFormatUtils.cachedParseDate(dateStr);
}
long end = System.currentTimeMillis();
log("cachedParseDate cost:" + (end - start) + "ms.");
}
/**
* 缓存SimpleDateFormat对象, 转换Date->String
*/
public void cachedFormatDateTest(Date date) {
long start = System.currentTimeMillis();
for(int i = 0; i < COUNT; i++) {
SimpleDateFormatUtils.cachedFormatDate(date);
}
long end = System.currentTimeMillis();
log("cachedFormatDate cost:" + (end - start) + "ms.");
}
/**
* 不缓存转换String->Date
*/
public void parseDateTest(String dateStr) {
long start = System.currentTimeMillis();
for(int i = 0; i < COUNT; i++) {
SimpleDateFormatUtils.parseDate(dateStr);
}
long end = System.currentTimeMillis();
log("ParseDate cost:" + (end - start) + "ms.");
}
/**
* 不缓存转换Date->String
*/
public void formatDateTest(Date date) {
long start = System.currentTimeMillis();
for(int i = 0; i < COUNT; i++) {
SimpleDateFormatUtils.formatDate(date);
}
long end = System.currentTimeMillis();
log("formatDate cost:" + (end - start) + "ms.");
}
public void log(String message) {
System.out.println(message);
}
public static void main(String[] args) {
SimpleDateFormatTest sdf = new SimpleDateFormatTest();
SimpleDateFormatTest.COUNT = 100000;
String dateStr = "2010-11-20 00:50:42.703";
sdf.cachedParseDateTest(dateStr);
sdf.parseDateTest(dateStr);
sdf.cachedFormatDateTest(new Date());
sdf.formatDateTest(new Date());
}
}
得到如下测试结果:
cachedParseDate cost: 593ms.
ParseDate cost: 1485ms.
cachedFormatDate cost: 328ms.
formatDate cost: 1187ms.
很明显的可以看出通过静态化SimpleDateFormat对象,Date->String 与 String->Date 速度提高了3倍以上.
但是如果在多线程环境下,会造成格式化日期错误, 因此需要借助于ThreadLocal来完成安全的日期格式化:
package com.haitao.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class SimpleDateFormatUtils {
public static final String DATE_PARTEN = "yyyy-MM-dd HH:mm:ss.SSS";
/**
* 线程安全转换 String -> Date
*/
public static Date safeParseDate(String dateStr) {
try {
return getFormat().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* 线程安全格式化 Date -> String
*/
public static String safeFormatDate(Date date) {
return getFormat().format(date);
}
/**
* 借助ThreadLocal完成对每个线程第一次调用时初始化SimpleDateFormat对象
*/
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
protected synchronized SimpleDateFormat initialValue() {
return new SimpleDateFormat(DATE_PARTEN);
}
};
/**
* 获取当前线程中的安全SimpleDateFormat对象
*/
private static DateFormat getFormat(){
return (DateFormat)threadLocal.get();
}
}
测试方法,同样使用10W次 字符串->Date与 10W次 Date> 字符串:
package com.haitao.test;
import java.util.Date;
import com.haitao.concurrency.FormatDateConcurrencyTask;
import com.haitao.concurrency.ParseDateConcurrencyTask;
public class SimpleDateFormatMutiThreadTest {
private static final int N = 100000;
public static void main(String[] args) throws Exception {
Date[] dateArray = SimpleDateFormatTest.genrateDate(N);
String[] stringArray = SimpleDateFormatTest.genrateDateStr(N);
// 并发任务, stringArray任务数据, 10000传入每个线程处理任务数据个数
// 这里会生成10个线程的线程池来处理
ParseDateConcurrencyTask pdct = new ParseDateConcurrencyTask(stringArray, 10000);
pdct.run();
// 并发任务, dataArray任务数据, 10000传入每个线程处理任务数据个数
// 这里会生成10个线程的线程池来处理
FormatDateConcurrencyTask fdct = new FormatDateConcurrencyTask(dateArray, 10000);
fdct.run();
}
}
并发的代码就不贴了,这个我自己写了一个单机的mapReduce并发任务框架,太乱,还没来得及整理,有时间给大家分享一下.
测试结果如下:
safeParseDate cost:359ms.
safeFormatDate cost:297ms.
可以看到测试结果比单线程cached模型都效率高,当然这里是由于多线程处理,在所有线程执行完毕进行最后统计,所以速度会这么快,在单线程效果下会比cached模型略微低一点,大概50ms左右的样子.
优化完毕,结论是通过在当前线程内缓存SimpleDateFormat既可以达到线程安全,又可以提升3倍以上的执行效率:)