java日期格式化SimpleDateFormat的使用
一、SimpleDateFormat类格式化符号
日期和时间模式(注意大小写,代表的含义是不同的) |
---|
yyyy:年 |
MM:月 |
dd:日 |
hh:12小时制(0-11) |
HH:24小时制(0-23) |
mm:分 |
ss:秒 |
S:毫秒 |
E:星期几 |
D:一年中的第几天 |
F:一月中的第几个星期(会把这个月总共过的天数除以7) |
w:一年中的第几个星期 |
W:一月中的第几星期(会根据实际情况来算) |
a:上下午标识 |
k:和HH差不多,表示一天24小时制(1-24)。 |
K:和hh差不多,表示一天12小时制(0-11)。 |
z:表示时区 |
举个栗子:如果你想显示日期格式为 2022-07-15 13:00:00 000
使用格式就应该为 yyyy-MM-dd HH:mm:ss SSS(要注意小时数的大小写之分,用错了你就可能会突然发现你的时间少了十二小时:)
二、SimpleDateFormat类的具体使用
-
Date 转String 可使用 format(Object obj),String转Date可使用parse(String source);
-
时间字串String转Date时,parse时如果时间字串与模式字串不匹配会报ParseException异常。
Date d = new Date();
Date d = new Date();
System.out.println(d);
// 要转换的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
SimpleDateFormat sdf1 = new SimpleDateFormat(
"一年中的第 D 天 一年中第w个星期 一月中第W个星期 E a 在一天中K时 z时区");
// 格式化日期,日期->字符串
String formatDate = sdf.format(d);
String formatDate1 = sdf1.format(d);
System.out.println(formatDate);
System.out.println(formatDate1);
String s = "2022-07-13 15:12:34 000";
try {
Date date = sdf.parse(s);//格式化日期,字符串->日期
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
运行后:
三、线程安全
通过阅读源码我们可以发现,在他的代码中提及了Date formats are not synchronized.也就是该方法是线程不安全的。因为SimpleDateFormat 继承自 DateFormat,在 DateFormat 中定义了一个 protected 属性的 Calendar 类对象:calendar。因为 Calendar 类牵扯到了时区与本地化,JDK 的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。
可以假设线程A执行完calendar.setTime(date),把时间设置成当前时间,这时候被挂起,线程B获得CPU执行权。线程B也执行到了calendar.setTime(date),把时间设置为新的当前时间。线程挂起,线程A继续走,calendar还会被继续使用(subFormat方法),而这时calendar用的是线程B设置的值了,出现时间不对,线程挂死问题等等。
解决方法
1.局部变量
最简单的一种方式就是将 SimpleDateFormat 类对象定义成局部变量,也就是遇到需要时间日期字串转换或格式化的时候,再new 一个SimpleDateFormat类对象。不过这样做的缺点就是再高并发的情况下会创建很多对象,影响程序性能。
2.DateTimeFormatter 方式
DateTimeFormatter 是 Java8 提供的新的日期时间 API 中的类,DateTimeFormatter 类是线程安全的,可以在高并发场景下直接使用 DateTimeFormatter 类来处理日期的格式化操作。代码如下所示:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class DateTimeFormatterTest {
/** DateTimeFormatter 对象 */
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/** 同时运行的线程数量 */
private static final int THREAD_COUNT = 20;
/** 执行总次数 */
private static final int EXECUTE_COUNT = 1000;
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(EXECUTE_COUNT);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < EXECUTE_COUNT; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
LocalDate.parse("2022-06-07", DTF);
} catch (InterruptedException e) {
System.out.println("获取信号量出错");
e.printStackTrace();
System.exit(1);
} catch (Exception e) {
System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
e.printStackTrace();
System.exit(1);
}
semaphore.release();
latch.countDown();
});
}
latch.await();
executorService.shutdown();
System.out.println("所有线程格式化日期成功");
}
}
3.使用ThreadLocal 方式
使用 ThreadLocal 存储每个线程拥有的 SimpleDateFormat 对象的副本,能够有效的避免多线程造成的线程安全问题,使用 ThreadLocal 解决线程安全问题的代码如下所示:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SimpleDateFormatTest {
/** 执行总次数 */
private static final int EXECUTE_COUNT = 1000;
/** 同时运行的线程数量 */
private static final int THREAD_COUNT = 20;
private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(EXECUTE_COUNT);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < EXECUTE_COUNT; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
SimpleDateFormat sdf = THREAD_LOCAL.get();
sdf.parse("2022-06-07");
} catch (InterruptedException e) {
System.out.println("获取信号量出错");
e.printStackTrace();
System.exit(1);
} catch (ParseException e) {
System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
e.printStackTrace();
System.exit(1);
} catch (NumberFormatException e) {
System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
e.printStackTrace();
System.exit(1);
}
semaphore.release();
latch.countDown();
});
}
latch.await();
executorService.shutdown();
System.out.println("所有线程格式化日期成功");
}
}
(关于线程问题解决方法参考:dh@SimpleDateFormat 类的线程安全问题)