乐趣区

关于程序员:在Java中你真的会日期转换吗

1. 什么是 SimpleDateFormat

在 java doc 对 SimpleDateFormat 的解释如下:

SimpleDateFormat 是用于以对语言环境敏感的形式格式化和解析日期的具体类。它容许格式化(日期→文本),解析(文本→日期)和规范化。

SimpleDateFormat 是一个用来对地位敏感的格式化和解析日期的实体类。他容许把日期格式化成文本,把文本解析成日期和规范化。

1.1 应用 SimpleDateFormat

simpleDateFormat 的应用办法比较简单:
`public static void main(String[] args) throws Exception {

SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

System.out.println(simpleDateFormat.format(new Date()));

System.out.println(simpleDateFormat.parse(“2018-07-09 11:10:21”));
}
`
1. 首先须要定义一个日期的模式,这里咱们定义的是“yyyy-mm-dd HH:mm:ss”,也就是咱们这个 simpleDateFormat 不论是格式化还是解析都须要依照这个模式。

2. 对于 format 须要传递 Date 的对象,会返回一个 String 类型,这个 String 会遵循咱们下面的格局生成。

3. 对于 parse 须要传递一个遵循上述模式的款式,如果传递谬误的模式会引发 java.text.ParseException 异样,如果传递正确的会生成一个日期对象。
` 附:格局占位符

G 年代标志符

y 年

M 月

d 日

h 时 在上午或下午 (1~12)

H 时 在一天中 (0~23)

m 分

s 秒

S 毫秒

E 星期

D 一年中的第几天

F 一月中第几个星期几

w 一年中第几个星期

W 一月中第几个星期

a 上午 / 下午 标记符

k 时 在一天中 (1~24)

K 时 在上午或下午 (0~11)

z 时区
`

2.SimpleDateFormat 的隐患

很多初学者,或者一些一些教训比拟浅的 java 开发工程师,用 SimpleDateFormat 会呈现一些奇奇怪怪的 BUG。

1. 后果值不对:转换的后果值常常会出乎意料,和预期不同,常常让很多人摸不着头脑。

2. 内存透露:因为转换的后果值不对,后续的一些操作,如一个循环,累加一天解决一个货色,然而生成的日期如果异样导致很大的话,则使这个循环变成一个相似死循环一样导致系统内存透露,从新触发 GC,造成零碎不可用。

为什么会呈现这么多问题呢?因为 SimpleDateFormat 线程不平安,很多人都会写一个 Util 类,而后把 SimpleDateFormat 定义成一对的一个常量,所有线程都共享这个常量:
`protected static final SimpleDateFormat dayFormat = new SimpleDateFormat(“yyyy-MM-dd”);

public static Date formatDate(String date) throws ParseException {

return dayFormat.parse(date);

}
`
为什么 SimpleDateFormat 会线程不平安呢,在 SimpleDateFormat 源码中,所有的格式化和解析都须要通过一个两头对象进行转换,那就是 Calendar,而这个也是咱们呈现线程不平安的罪魁祸首,试想一下当咱们有多一个线程操作同一个日历,过后的线程会笼罩先来线程的数据,那最初实际上返回的是起初线程的数据,这样就导致咱们下面所述的 BUG 的产生:
`/ Called from Format after creating a FieldDelegate

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;

}
`

3. 如何避坑

对于 SimpleDateFormat 的解决办法有以下几种:

3.1 新建 SimpleDateFormat

下面呈现谬误的起因是因为所有线程都共享一个 SimpleDateFormat,这里有个比拟好解决的办法,每次应用的时候都创立一个新的 SimpleDateFormat,咱们能够在 DateUtils 中将创立 SimpleDateFormat 放在办法外部:
`public static Date formatDate(String date) throws ParseException {

SimpleDateFormat dayFormat = new SimpleDateFormat(“yyyy-MM-dd”);

return dayFormat.parse(date);

}
`
下面这个办法尽管能解决咱们的问题然而约会了另外一个问题就是,如果这个办法使用量比拟大,有可能会替换造成 Young gc,整个零碎还是会受肯定的影响。

3.2 应用 ThreadLocal

应用 ThreadLocal 能防止下面的从新造成的年老 gc,咱们对每个线程都应用 ThreadLocal 进行保留,因为 ThreadLocal 是线程之间隔离开的,所以不会呈现线程平安问题:
`private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();

public static Date formatDate(String date) throws ParseException {

SimpleDateFormat dayFormat = getSimpleDateFormat();

return dayFormat.parse(date);

}

private static SimpleDateFormat getSimpleDateFormat() {

SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();

if (simpleDateFormat == null){

simpleDateFormat = new SimpleDateFormat(“yyyy-mm-dd HH:mm:ss”)

simpleDateFormatThreadLocal.set(simpleDateFormat);

}

return simpleDateFormat;

}
`
3.3 应用第三方工具包

尽管下面的 ThreadLocal 能解决咱们呈现的问题,然而第三方工具包提供的性能更弱小,在 java 中有两个类库比拟闻名一个是 Joda-Time,一个是 Apache common 包

3.3.1 举荐(Joda-Time)

对于咱们简单的操作都能够应用 Joda-Time 操作,上面我两个两个例子,对于把日期加上 90 天,如果应用原生的 Jdk 咱们须要这样写:
`Calendar calendar = Calendar.getInstance();

calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);

SimpleDateFormat sdf =

new SimpleDateFormat(“E MM/dd/yyyy HH:mm:ss.SSS”);

calendar.add(Calendar.DAY\_OF\_MONTH, 90);

System.out.println(sdf.format(calendar.getTime()));
`
然而在咱们的 joda-time 中只须要两句话,并且 api 也比拟通俗易懂,所以你为什么不必 Joda-Time 呢?
`DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);

System.out.println(dateTime.plusDays(90).toString(“E MM/dd/yyyy HH:mm:ss.SSS”);`
3.3.2 common-lang 包

在 common-lang 包中有个类叫 FastDateFormat,因为 common-lang 这个包根本被很多 Java 我的项目都会援用,所以你能够不必专门去援用解决工夫包,即可解决工夫,在 FastDateFormat 中每次解决工夫的过后会创立一个日历,应用办法比较简单代码如下所示:
`FastDateFormat.getInstance()。format(new Date());
`
3.4 降级 jdk8(举荐)

在 java8 中 Date 这个类中的很多办法包含构造方法都被打上了 @不倡议应用废除的注解,取而代之的是 LocalDateTime,LocalDate LocalTime 这三个类:

LocalDate 无奈蕴含工夫;

LocalTime 无奈蕴含日期;

LocalDateTime 能同时蕴含日期和工夫。

如果你是 Java8,那你肯定要应用他,在日期的格式化和解析方面不必思考线程安全性,代码如下:
`public static String formatTime(LocalDateTime time,String pattern) {

return time.format(DateTimeFormatter.ofPattern(pattern));

}
`
当然 localDateTime 是 java8 的一大亮点,当然不仅仅只是解决了线程平安的问题,同样也提供了一些其余的运算变量加减天数:
`// 日期加上一个数, 依据 field 不同加不同值,field 为 ChronoUnit.*

public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {

return time.plus(number, field);

}

// 日期减去一个数, 依据 field 不同减不同值,field 参数为 ChronoUnit.*

public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field){

return time.minus(number,field);

}
`
最初,如果您放心应用 LocalDateTime 合并您现有的代码产生很大的扭转的话,那你能够将他们两进行互转:
`//Date 转换为 LocalDateTime

public static LocalDateTime convertDateToLDT(Date date) {

return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());

}

//LocalDateTime 转换为 Date

public static Date convertLDTToDate(LocalDateTime time) {

return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());

}
`

退出移动版