合理使用SimpleDateFormat

1.簡單使用

public class SimpleUse {

	public static void main(String[] args) throws ParseException {
		//創建SimpleDateFormat對象
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		
		//常見用法一:格式化日期爲想要的字符串
		String dateStr = sdf.format(new Date(System.currentTimeMillis()));
		System.out.println(dateStr);
		
		//常見用法二:解析字符串爲日期
		Date date = sdf.parse("2015-06-29 22:12:13");
		System.out.println(date);
	}
}

可以得到如下的運行結果:

<span style="font-size:14px;">2015-06-29 22:12:57
Mon Jun 29 22:12:13 CST 2015</span>

2.性能消耗

創建SimpleDateFormat是非常消耗性能的。

public class Problem1 {

	public static void main(String[] args) throws ParseException {
		String dateStr = "2015-06-29 12:22:22";
		String pattern = "yyyy-MM-dd HH:mm:ss";
		test1(dateStr,pattern);
		test2(dateStr,pattern);
	}
	public static void test1(String dateStr,String pattern) throws ParseException {
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			SimpleDateFormat sdf = new SimpleDateFormat(pattern);
			sdf.parse(dateStr);
		}
		long end = System.currentTimeMillis();
		System.out.println("cost1=" + (end - start));
	}
	public static void test2(String dateStr,String pattern) throws ParseException {
		Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			if (!map.containsKey(pattern)) {
				map.put(pattern, new SimpleDateFormat(pattern));
			}
			SimpleDateFormat sdf = map.get(pattern);
			sdf.parse(dateStr);
		}
		long end = System.currentTimeMillis();
		System.out.println("cost2=" + (end - start));
	}
}

運行得到如下結果:

cost1=640
cost2=62

如上所示,代碼中循環了10000次的反覆調用,最後的時間消耗還是比較明顯的。


可能你會覺得創建對象本身就會消耗性能,我先開始也是有這個疑問,看下面這段代碼也許會比較直觀。

public class Problem2 {

	public static void main(String[] args) throws ParseException {
		String dateStr = "2015-06-29 12:22:22";
		String pattern = "yyyy-MM-dd HH:mm:ss";
		test1(dateStr, pattern);
		test2(dateStr, pattern);
	}

	public static void test1(String dateStr, String pattern) throws ParseException {
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			SomeObj sdf = new SomeObj(pattern);
			sdf.parse(dateStr);
		}
		long end = System.currentTimeMillis();
		System.out.println("cost1=" + (end - start));
	}

	public static void test2(String dateStr, String pattern) throws ParseException {
		Map<String, SomeObj> map = new HashMap<String, SomeObj>();
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			if (!map.containsKey(pattern)) {
				map.put(pattern, new SomeObj(pattern));
			}
			SomeObj sdf = map.get(pattern);
			sdf.parse(dateStr);
		}
		long end = System.currentTimeMillis();
		System.out.println("cost2=" + (end - start));
	}
}
運行得到如下結果:

cost1=2
cost2=10

這裏的SomeObj只是一個比較的對象而已,不用去管裏面是怎麼寫的。

比較這兩段代碼可知:

SimpleDateFormat的創建是很消耗性能的。


解決辦法:

private static Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
	public static SimpleDateFormat getInstance(String pattern) {
		if (!map.containsKey(pattern)) {
                     map.put(pattern, new SimpleDateFormat(pattern));
                  }
        return map.get(pattern);
}

如上代碼段所示,使用單例模式的確會提升性能,但這樣做還是不夠的。


3.安全性

SimepleDateFormat是非線程安全的。具體解釋我不敢肯定。應該是在調用parse、format的時候會使用到一個Calendar對象。每次使用的時候,calendar會先後調用clean、getTime方法。這樣如果多線程公用同一個calendar是會出問題的。(具體源代碼日後再分析補充,如果描述不正確還請指出)。

具體的效果我們看下代碼。

public class MultipleThreadDemo implements Runnable {
	String pattern;
	public MultipleThreadDemo(String pattern) {
		this.pattern = pattern;
	}
	@Override
	public void run() {
		SimpleDateFormat sdf = getInstance();
		int count = 10;
		while (count-- > 0) {
			try {
				sdf.parse(pattern);
				sdf.format(new Date(System.currentTimeMillis()));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		MultipleThreadDemo d1 = new MultipleThreadDemo("2015-06-21");
		MultipleThreadDemo d2 = new MultipleThreadDemo("2015-06-22");
		new Thread(d1).start();
		new Thread(d2).start();
	}
	private static SimpleDateFormat sdf_ = new SimpleDateFormat("yyyy-MM-dd");
	public static SimpleDateFormat getInstance() {
		return sdf_;
	}
}

運行得到如下結果:

java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110)
	at java.lang.Double.parseDouble(Double.java:540)
	at java.text.DigitList.getDouble(DigitList.java:168)
	at java.text.DecimalFormat.parse(DecimalFormat.java:1321)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
	at java.text.DateFormat.parse(DateFormat.java:355)
	at com.cp.demo.SimpleDateFormatDemo.MultipleThreadDemo.run(MultipleThreadDemo.java:20)
	at java.lang.Thread.run(Thread.java:745)
這樣的話,多線程共同使用同一個SimpleDateFormat是不行的。

4.最終解決辦法

public class SolveDemo implements Runnable {
	String pattern;
	public SolveDemo(String pattern) {
		this.pattern = pattern;
	}
	@Override
	public void run() {
		SimpleDateFormat sdf = getInstance();
		int count = 100;
		while(count-->0){
			try {
				sdf.parse(pattern);
				sdf.format(new Date(System.currentTimeMillis()));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		SolveDemo d1 = new SolveDemo("2015-06-21");
		SolveDemo d2 = new SolveDemo("2015-06-22");

		new Thread(d1).start();
		new Thread(d2).start();
	}

	private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>();

	public static SimpleDateFormat getInstance() {
		SimpleDateFormat sdf = SolveDemo.threadLocal.get();
		if (sdf != null) {
			return sdf;
		}
		SimpleDateFormat sdf_ = new SimpleDateFormat("yyyy-MM-dd");
		System.out.println(Thread.currentThread().getName()+":創建一個sdf");
		SolveDemo.threadLocal.set(sdf_);
		return sdf_;

	}

}
運行得到如下結果:

Thread-0:創建一個sdf
Thread-1:創建一個sdf
這裏使用了ThreadLocal爲每個線程創建了單獨的一個SimpleDateFormat實例,有效地防止了多個線程之間的干擾。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章