Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

SimpleDateFormat、FastDateFormat和Joda-Time

SimpleDateFormat是线程不安全的,不能多个线程共用。而FastDateFormat和Joda-Time都是线程安全的。

SimpleDateFormat是JDK提供的,不需要依赖第三方jar包,而其他两种都得依赖第三方jar包。
FastDateFormatapache的commons-lang3包提供的:

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>

Joda-Time需要依赖以下maven的配置。

1
2
3
4
5
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>

SimpleDateFormat 和 FastDateFormat 主要都是对时间的格式化。

SimpleDateFormat

SimpleDateFormat在对时间进行格式化的方法format中,会先对 calendar 对象进行 setTime 的赋值,若是有多个线程同时操作一个 SimpleDateFormat 实例的话,就会对calendar的赋值进行覆盖,进而产生问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);

boolean useDateFormatSymbols = useDateFormatSymbols();

for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}

switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;

case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;

default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}

calendar 是java.text.DateFormat中的属性

1
2
3
4
5
6
/**
The Calendar instance used for calculating the date-time fields and the instant of time. This field is used for both formatting and parsing.
Subclasses should initialize this field to a Calendar appropriate for the Locale associated with this DateFormat.
**/
protected Calendar calendar;

一个SimpleDateFormat实例的 calendar 属性是多线程共享的。

有三种方法可以解决这个问题:

  1. 在每次需要使用的时候,进行SimpleDateFormat实例的创建,这种方式会导致创建多个对象实例,占用内存,不建议使用。
  2. 使用同步的方式,在调用方法的时候加上synchronized,这样可以让线程调用方法时,进行加锁,也就是会造成线程间的互斥,但对性能影响比较大。
  3. 使用ThreadLocal进行保存,相当于一个线程只会有一个实例,进而减少了实例数量,也防止了线程间的互斥,推荐使用这种方式。

FastDateFormat

FastDateFormat 是线程安全的,可以直接使用,不必考虑多线程的情况

FastDateFormat的实例是通过getInstance方法创建的。

1
2
FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");  
System.out.println(format.format(new Date()));

可以使用 org.apache.commons.lang3.time.DateFormatUtils 类来操作(也是commons.lang3提供的),方法里面也是使用的FastDateFormat类来实现的:

1
2
3
4
public static String format(final Date date, final String pattern, final TimeZone timeZone, final Locale locale) {
final FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);
return df.format(date);
}

使用方式

1
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));  

Joda-Time

Joda-Time 与以上两种有所区别,不仅仅可以对时间进行格式化输出,而且可以生成瞬时时间值,并与Calendar、Date等对象相互转化,极大的方便了程序的兼容性。

官网地址 https://www.joda.org/joda-time/

Joda-Time的类具有不可变性,因此他们的实例是无法修改的,就跟String的对象一样。这种不可变性提现在所有API方法中,这些方法返回的都是新的类实例,与原来实例不同。

以下是 Joda-Time 的一些使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 得到当前时间  
Date currentDate = new Date();
DateTime dateTime = new DateTime(); // DateTime.now()

System.out.println(currentDate.getTime());
System.out.println(dateTime.getMillis());

// 指定某一个时间,如2016-08-29 15:57:02
Date oneDate = new Date(1472457422728L);
DateTime oneDateTime = new DateTime(1472457422728L);
DateTime oneDateTime1 = new DateTime(2016, 8, 29, 15, 57, 2, 728);

System.out.println(oneDate.toString());
System.out.println(oneDateTime.toString()); // datetime默认的输出格式为yyyy-MM-ddTHH:mm:ss.SSS
System.out.println(oneDateTime1.toString("MM/dd/yyyy hh:mm:ss.SSSa")); // 直接就可以输出规定的格式

// DateTime和Date之间的转换
Date convertDate = new Date();
DateTime dt1 = new DateTime(convertDate);
System.out.println(dt1.toString());

Date d1 = dt1.toDate();
System.out.println(d1.toString());

// DateTime和Calendar之间的转换
Calendar c1 = Calendar.getInstance();
DateTime dt2 = new DateTime(c1);
System.out.println(dt2.toString());

Calendar c2 = dt2.toCalendar(null); // 默认时区Asia/Shanghai
System.out.println(c2.getTimeZone());

// 时间格式化
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime dt3 = DateTime.parse("2016-08-29 13:32:33", formatter);
System.out.println(dt3.toString());
// 若是不指定格式,会采用默认的格式,yyyy-MM-ddTHH:mm:ss.SSS,若被解析字符串只到年月日,后面的时分秒会全部默认为0
DateTime dt4 = DateTime.parse("2016-08-29T");
System.out.println(dt4.toString());
// 输出locale 输出2016年08月29日 16:43:14 星期一
System.out.println(new DateTime().toString("yyyy年MM月dd日 HH:mm:ss EE", Locale.CHINESE));

// 计算两个日期间隔的天数
LocalDate start = new DateTime().toLocalDate();
LocalDate end = new LocalDate(2016, 8, 25);
System.out.println(Days.daysBetween(start ,end).getDays()); // 这里要求start必须早于end,否则计算出来的是个负数
// 相同的还有间隔年数、月数、小时数、分钟数、秒数等计算
// 类如Years、Hours等

// 对日期的加减操作
DateTime dt5 = new DateTime();
dt5 = dt5.plusYears(1) // 增加年
.plusMonths(1) // 增加月
.plusDays(1) // 增加日
.minusHours(1) // 减小时
.minusMinutes(1) // 减分钟
.minusSeconds(1); // 减秒数
System.out.println(dt5.toString());

// 判断是否闰月
DateTime dt6 = new DateTime();
DateTime.Property month = dt6.monthOfYear();
System.out.println(month.isLeap());