万能的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倍以上的执行效率:)