`
tuhaitao
  • 浏览: 375398 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

高性能的SimpleDateFormat

    博客分类:
  • java
 
阅读更多

以前写过一篇 , 《SimpleDateFormat性能调优http://tuhaitao.iteye.com/admin/blogs/822277

虽然可以解决SimpleDateFormat的线程安全问题,但不能够灵活的配置日期Parten,为了使其能够灵活的配置日期格式,我进行了一番改造。

 

1. 通过配置文件加载Praten,灵活配置日期格式

 

2. 使用FastHashMap结合ThreadLocal,解决多个日期格式的线程安全问题

 

 

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencfg.core;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.apache.commons.collections.FastHashMap;
import org.apache.commons.lang.StringUtils;


/**
 * <p>
 * DateFormat Utils
 * <p>
 * 
 * <p>#ThreadSafe#</p>
 * 
 * @author Opencfg Software Foundation
 * @since 0.0.1-SNAPSHOT
 * @version $Id: DateFormatUtils.java 2011-06-14 23:21:53 reymondtu $
 */
public class DateFormatUtils {
	
	private static final FastHashMap datePartenMap = new FastHashMap();
	
	static {
		// load data parten properties files
		List<String> propList = PropertiesUtils.getSimpleList("DateFormatUtils.properties") ;
		datePartenMap.setFast(true);
		for(String dateParten : propList) {
			if(StringUtils.startsWith(dateParten, "#")) continue;
			datePartenMap.put(StringUtils.trim(dateParten), new SimpleDateFormat(dateParten));
		}
	}
	
	/**
	 * String -> Date
	 * 
	 * @param dateParten Parse Date Parten
	 * @param dateStr    Parse Date String
	 * @throws ParseException 
	 */
    public static Date safeParseDate(final String dateParten, final String dateStr) throws ParseException {
		return getFormat(dateParten).parse(dateStr);
    }
    
    /**
	 * Date -> String
	 * 
	 * @param dateParten Format Date Parten
	 * @param date       Format java.util.Date Object
	 */
    public static String safeFormatDate(final String dateParten, final Date date) {
        return getFormat(dateParten).format(date);
    }
  
    private static ThreadLocal<FastHashMap> threadLocal = new ThreadLocal<FastHashMap>(){
        protected synchronized FastHashMap initialValue() {
            return (FastHashMap)datePartenMap.clone();
        }
    };
  
    private static DateFormat getFormat(final String dateParten){
        return (DateFormat)threadLocal.get().get(dateParten);
    }
	
}
 

 

这里使用了apache commons-collections里的FashHashMap类,此类适用于读多写少的高并发操作,很适合写缓存.

 

 

 

下边是读取配置文件的类:

 

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencfg.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.lang.StringUtils;

/**
 * <p>
 * Properties Utils
 * <p>
 * 
 * <p>#ThreadSafe#</p>
 * 
 * @author Opencfg Software Foundation
 * @since 0.0.1-SNAPSHOT
 * @version $Id: PropertiesUtils.java 2011-06-15 01:22:53 reymondtu $
 */
public class PropertiesUtils {
	
	/**
	 * Load Properties File
	 * @throws ConfigurationException 
	 */
	public static PropertiesConfiguration loadClassPathProperties(final String fileName, final long refreshInterval) throws ConfigurationException {
		URL url = PropertiesUtils.class.getClassLoader().getResource(fileName);
		File f = new File(url.getFile());
		if(!f.exists()) {
			throw new IllegalArgumentException("can not load file " + fileName);
		}
		PropertiesConfiguration prop = new PropertiesConfiguration(url.getFile());
		FileChangedReloadingStrategy fcrs = null;
		fcrs = new FileChangedReloadingStrategy();
		fcrs.setRefreshDelay(refreshInterval);
		prop.setReloadingStrategy(fcrs);
		return prop;
	}
	
	public static List<String> getSimpleList(final String fileName) {
		try {
			PropertiesConfiguration prop = loadClassPathProperties(fileName, 500);
			BufferedReader reader = new BufferedReader(new FileReader(prop.getFile()));
	        List<String> list = new ArrayList<String>();
	        String line = null;
	        while ((line =reader.readLine())!= null) {
	        	if(StringUtils.startsWith(line, "#")) {
	        		continue;
	        	}
	            list.add(line);
	            line = reader.readLine();
	        }
			return list;
		} catch (Exception e) {
			throw new IllegalArgumentException("can not load file " + fileName);
		}
	}
	
}

 

 

配置文件DateFormatUtils.properties如下:

 

 

#show all
yyyy-MM-dd HH:mm:ss
yyyy年MM月dd日 HH:mm:ss
yyyy年MM月dd日 HH时mm分ss秒

#only show date
yyyy-MM-dd
yyyy年MM月dd日

#only show hour,minute,second
HH:mm:ss
HH时mm分ss秒
 

这里我跟传统的SimpleDateFormat,apache 的 FastDateForma, DateUtilst做了性能对比:

 

 

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencfg.core;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang.time.FastDateFormat;
import org.junit.Before;
import org.junit.Test;

/**
 * DateFormatUtils JUnit4 Test
 * 
 * @author Opencfg Software Foundation
 * @since 0.0.1-SNAPSHOT
 * @version $Id: DateFormatUtilsTest.java 2011-06-15 00:36:53 reymondtu $
 */
public class DateFormatUtilsTest {
	private final int TEST_SIZE = 1000000;
	private Date[] dateArray = new Date[TEST_SIZE];
	private String[] stringArray = new String[TEST_SIZE];
	
	@Before
	public void setUp() {
		for(int i = 0; i < TEST_SIZE; i++) {
			dateArray[i] = new Date();
		}
		
		for(int i = 0; i < TEST_SIZE; i++) {
			stringArray[i] = DateFormatUtils.safeFormatDate("yyyy-MM-dd HH:mm:ss", new Date());
		}
	}
	
	/*  Format */
	
	@Test
	public void test_JDK_SimpleDateFormat_format() {
		long starttime = System.currentTimeMillis();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		for(Date date : dateArray) {
			sdf.format(date);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("JDK     SimpleDateFormat format cost:" + (endtime - starttime) + "ms");
	}
	
	@Test
	public void test_Opencfg_DateFormatUtils_format() {
		long starttime = System.currentTimeMillis();
		for(Date date : dateArray) {
			DateFormatUtils.safeFormatDate("yyyy-MM-dd HH:mm:ss", date);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Opencfg DateFormatUtils  format cost:" + (endtime - starttime) + "ms");
	}
	
	@Test
	public void test_Apache_FastDateFormat_format() {
		FastDateFormat fastDateFormat = FastDateFormat.getInstance();
		long starttime = System.currentTimeMillis();
		for(Date date : dateArray) {
			fastDateFormat.format(date);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Apache  FastDateFormat   format cost:" + (endtime - starttime) + "ms");
	}
	
	
	/*  Parse */
	
	@Test
	public void test_JDK_SimpleDateFormat_parse() throws ParseException {
		long starttime = System.currentTimeMillis();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		for(String str : stringArray) {
			sdf.parse(str);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("JDK     SimpleDateFormat parse  cost:" + (endtime - starttime) + "ms");
	}
	
	
	@Test
	public void test_Opencfg_DateFormatUtils_parse() throws ParseException {
		long starttime = System.currentTimeMillis();
		for(String str : stringArray) {
			DateFormatUtils.safeParseDate("yyyy-MM-dd HH:mm:ss", str);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Opencfg DateFormatUtils  parse  cost:" + (endtime - starttime) + "ms");
	}
	
	@Test
	public void test_Apache_DateUtils_parse() throws ParseException {
		long starttime = System.currentTimeMillis();
		String[] dateParten = new String[]{"yyyy-MM-dd HH:mm:ss"};
		for(String str : stringArray) {
			org.apache.commons.lang.time.DateUtils.parseDate(str, dateParten);
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Apache  DateUtils        parse  cost:" + (endtime - starttime) + "ms");
	}
	


}

 

 

这里分别用100W个string数组 与100W个Date数组做了实验,结果如下

 

 

JDK         SimpleDateFormat  format cost:1313ms
Opencfg  DateFormatUtils     format cost:844ms
Apache   FastDateFormat     format cost:1860ms
JDK         SimpleDateFormat parse  cost:3563ms
Opencfg  DateFormatUtils    parse  cost:3641ms
Apache    DateUtils              parse  cost:16656ms

 

 

在单线程测试中,Opencfg DateFormatUtils的format能力 明显高于JDK 40%、Aapache 50%, parse的能力与单线成下的SimpleDateFormat相当, 与Apache的DateUtils明显不在一个级别,性能高出500%。

 

 

下边我使用多线程测试:

 

 

 

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencfg.core;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang.time.FastDateFormat;
import org.junit.Before;
import org.junit.Test;

/**
 * DateFormatUtils JUnit4 Test
 * 
 * @author Opencfg Software Foundation
 * @since 0.0.1-SNAPSHOT
 * @version $Id: DateFormatUtilsMutiThreadTest.java 2011-07-18 23:06:53 reymondtu $
 */
public class DateFormatUtilsMutiThreadTest {

	private final int TEST_SIZE = 1000000;
	private final int TEST_THREAD_COUNT = 10;
	private Date[] dateArray = new Date[TEST_SIZE];
	private String[] stringArray = new String[TEST_SIZE];
	
	@Before
	public void setUp() {
		for(int i = 0; i < TEST_SIZE; i++) {
			dateArray[i] = new Date();
		}
		
		for(int i = 0; i < TEST_SIZE; i++) {
			stringArray[i] = DateFormatUtils.safeFormatDate("yyyy-MM-dd HH:mm:ss", new Date());
		}
	}
	
/*  Format */
	
	@Test
	public void test_Apache_FastDateFormat_format() throws InterruptedException {
		final FastDateFormat fastDateFormat = FastDateFormat.getInstance();
		long starttime = System.currentTimeMillis();
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						fastDateFormat.format(dateArray[j]);
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Apache  Muti-Thread FastDateFormat   format cost:" + (endtime - starttime) + "ms");
	}
	
	@Test
	public void test_JDK_SimpleDateFormat_format() throws InterruptedException {
		long starttime = System.currentTimeMillis();
		final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						sdf.format(dateArray[j]);
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("JDK     Muti-Thread SimpleDateFormat format cost:" + (endtime - starttime) + "ms");
	}
	
	
	@Test
	public void test_Opencfg_DateFormatUtils_format() throws InterruptedException {
		long starttime = System.currentTimeMillis();
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						DateFormatUtils.safeFormatDate("yyyy-MM-dd HH:mm:ss", dateArray[j]);
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Opencfg Muti-Thread DateFormatUtils  format cost:" + (endtime - starttime) + "ms");
	}
	
	/*  Parse */
	
	@Test
	public void test_Apache_DateUtils_parse() throws ParseException, InterruptedException {
		long starttime = System.currentTimeMillis();
		final String[] dateParten = new String[]{"yyyy-MM-dd HH:mm:ss"};
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						try {
							org.apache.commons.lang.time.DateUtils.parseDate(stringArray[j], dateParten);
						} catch (ParseException e) {
							e.printStackTrace();
						}
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Apache  Muti-Thread DateUtils        parse  cost:" + (endtime - starttime) + "ms");
	}
	
	@Test
	public void test_JDK_SimpleDateFormat_parse() throws ParseException, InterruptedException {
		long starttime = System.currentTimeMillis();
		final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						try {
							sdf.parse(stringArray[j]);
						} catch (ParseException e) {
							e.printStackTrace();
						}
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("JDK     Muti-Thread SimpleDateFormat parse  cost:" + (endtime - starttime) + "ms");
	}
	
	
	@Test
	public void test_Opencfg_DateFormatUtils_parse() throws ParseException, InterruptedException {
		long starttime = System.currentTimeMillis();
		int avr = TEST_SIZE/TEST_THREAD_COUNT;
		for(int i = 0; i < TEST_THREAD_COUNT; i ++) {
			final int start =    i    * avr;
			final int end   = (i + 1) * avr;
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = start; j < end ; j ++) {
						try {
							DateFormatUtils.safeParseDate("yyyy-MM-dd HH:mm:ss", stringArray[j]);
						} catch (ParseException e) {
							e.printStackTrace();
						}
					}
				}
			});
			t.start();
			t.join();
		}
		long endtime = System.currentTimeMillis();
		System.out.println("Opencfg Muti-Thread DateFormatUtils  parse  cost:" + (endtime - starttime) + "ms");
	}
	
}

 

 

这里还是使用了100W个String,100W个Date, 每个测试开启10个线程, 测试结果如下:

 

 

Apache   Muti-Thread FastDateFormat   format cost:2265ms
JDK         Muti-Thread SimpleDateFormat format cost:766ms
Opencfg  Muti-Thread DateFormatUtils  format cost:891ms
Apache   Muti-Thread DateUtils        parse  cost:16766ms
JDK         Muti-Thread SimpleDateFormat parse  cost:3594ms
Opencfg Muti-Thread DateFormatUtils  parse  cost:3860ms

 

 

从结果中可以看出Opencfg的DateFormatUtils性能接近与SimpleDateFormat,远远高出Apache的FastDateFormat与DateUtils,同时又提供了基于ThreadLocal的线程安全保障。:)

1
0
分享到:
评论
2 楼 tuhaitao 2011-09-06  
heritrix 写道
    private static DateFormat getFormat(final String dateParten){ 
        return (DateFormat)threadLocal.get().get(dateParten); 
    }


这里threadLocal取出来, 怎么不用close?

你说的是ThreadLocal的remove()吧,貌似木有close()方法,

这里肯定不能remove,因为要缓存,如果remove了,每次都要new SimpleDateFormat,性能很低
1 楼 heritrix 2011-07-19  
    private static DateFormat getFormat(final String dateParten){ 
        return (DateFormat)threadLocal.get().get(dateParten); 
    }


这里threadLocal取出来, 怎么不用close?

相关推荐

Global site tag (gtag.js) - Google Analytics