DateTimeFormatterとSimpleDateFormatの速度比較
java8で導入されたDateTimeFormatterはスレッドセーフで、SimpleDateFormatと比べてインスタンス生成の分、速度が速いということなのでベンチマークをとってみる。
DateTimeFormatterはスレッドセーフ
実際に測る前にDateTimeFormatterはスレッドセーフなのか、ということを確認しておく。
まずは SimpleDateFormatがスレッドセーフでないことの確認。
現在日時に対し、0から指定回数を年月日時分秒それぞれに足したものを文字列に変換する処理を並行で実行し、出力する。
SimpleDateFormatをクラス変数として定義しているのに注意。
public class DateFormatterTest {
class SimpleDateFormatThreadTest {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void exec(int count) {
IntStream.range(0, count)
.mapToObj(i -> {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, i);
cal.add(Calendar.MONTH, i);
cal.add(Calendar.DATE, i);
cal.add(Calendar.HOUR_OF_DAY, i);
cal.add(Calendar.MINUTE, i);
cal.add(Calendar.SECOND, i);
return cal.getTime();
})
.map(d -> d + ":" + sdf.format(d))
.parallel()
.forEach(System.out::println);
}
}
public static void main(String[] args) {
new DateFormatterTest().go();
}
public void go() {
int count = 20;
new SimpleDateFormatThreadTest().exec(count);
}
}
結果は以下のように、Dateを単純に出力したものと変換後が異なることがある。
Wed Aug 21 10:01:06 JST 2030:2030-08-21 10:01:06 // 正しい
Tue Aug 08 21:48:54 JST 2017:2017-08-21 10:01:06 // 不正
再現しない場合は何度か実行するか、countを調整してみる。
次にDateTimeFoematterの場合
public class DateFormatterTest {
class DateTimeFormatterThreadTest {
DateTimeFormatter df = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
public void exec(int count) {
IntStream.range(0, count)
.mapToObj(i -> LocalDateTime.now()
.plusYears(i)
.plusMonths(i)
.plusDays(i)
.plusHours(i)
.plusMinutes(i)
.plusSeconds(i)
)
.map(d -> d + ":" + df.format(d))
.parallel()
.forEach(System.out::println);
}
}
public static void main(String[] args) {
new DateFormatterTest().go();
}
public void go() {
int count = 20;
new DateTimeFormatterThreadTest().exec(count);
}
}
結果はこんな感じ。何度やっても正しく変換されているはずである。
2029-07-20T09:08:12.865:2029-07-20 09:08:12
2030-08-21T10:09:13.867:2030-08-21 10:09:13
もちろんこれでスレッドセーフであることの証明にはならないが、大丈夫そう。
速度測定
SimpleDateFormatがスレッドセーフでないため毎回インスタンスを生成しなければならないので、その時点で比較するまでもないが、一応確認する。
まずは速度比較のための基底クラス。
repeatの分だけrun()を繰り返してforEach。ただし何もしない。
各変換処理はこのクラスを継承する。
abstract class SpeedTest {
LocalDateTime start;
void start() {
start = LocalDateTime.now();
}
void end() {
LocalDateTime end = LocalDateTime.now();
Duration d = Duration.between(start, end);
System.out.println(getClass().getName() + " time:" + d);
}
void exec(int repeat) {
start();
IntStream.range(0, repeat)
.mapToObj(run())
.forEach(x -> {});
end();
}
abstract IntFunction<String> run();
}
以下、SimpleDateFormat, DateTimeFormatter, ついでにString.format()を使った変換処理。
class SimpleDateFormatSpeedTest extends SpeedTest {
@Override
IntFunction<String> run() {
return i -> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
};
}
}
class DateTimeFormatterSpeedTest extends SpeedTest {
DateTimeFormatter df = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); // 年はuuuuであることに注意
@Override
IntFunction<String> run() {
return i -> df.format(LocalDateTime.now());
}
}
class StringFormatterSpeedTest extends SpeedTest {
@Override
IntFunction<String> run() {
return i -> String.format("%tF %<tT", new Date());
}
}
最後に実行するところ。
public void go() {
int repeat = 100;
new SimpleDateFormatSpeedTest().exec(repeat);
new DateTimeFormatterSpeedTest().exec(repeat);
new StringFormatterSpeedTest().exec(repeat);
}
実行結果。
やはり、DateTimeFormatterが速いが、String.formatも健闘している。
DateFormatterTest$SimpleDateFormatSpeedTest time:PT0.168S
DateFormatterTest$DateTimeFormatterSpeedTest time:PT0.016S
DateFormatterTest$StringFormatterSpeedTest time:PT0.022S
結論
DateTimeFormatterを使おう。
今回のソースはこちら