Java 7で導入されたNew I/O API(NIO2)。その時に新規クラスとして、java.nio.Files
などが増えました。
このクラスで、java.io.File
のコードを置き換えることなどが可能です。
今はjava.io.File
クラスではなく、NIO2の機能(java.nio.Files
クラス)を使うのが一般的だと思います。
例えばファイルの存在チェックをする場合は、
旧来は、java.io.File#exists
と やっていたものを java.nio.Files#exists
で置き換え可能です。
が、Java 8 環境だと java.nio.Files#exists
は、対象パスが存在しない場合にパフォーマンスが出ない というバグがあるようです。
JMHでベンチマークとって確認してみます。
ベンチマーク取得用のコードは以下の通りです。
(java.io.File#exists
と java.nio.Files#exists
を、対象ファイルが存在する場合と存在しない場合に分けて実行して、スループット計測しています)
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; @Warmup(iterations=10) @OutputTimeUnit(TimeUnit.MILLISECONDS) @BenchmarkMode(Mode.Throughput) @Fork(1) public class FileExistsBenchmark { /** 存在するファイルのパス */ private static final Path existsFilePath = Paths.get("sample1.txt"); /** 存在しないファイルのパス */ private static final Path notExistsFilePath = Paths.get("sample999.txt"); @Benchmark public void Files_exists_found() { // 存在するファイルに対する Files#exists Files.exists(existsFilePath); } @Benchmark public void Files_exists_not_found() { // 存在しないファイルに対する Files#exists Files.exists(notExistsFilePath); } @Benchmark public void File_exists_found() { // 存在するファイルに対する File#exists existsFilePath.toFile().exists(); } @Benchmark public void File_exists_not_found() { // 存在しないファイルに対する File#exists notExistsFilePath.toFile().exists(); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(FileExistsBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } }
macOS + Java 8 でのベンチマーク
macOS + Java 8 でのベンチマークは以下の通りとなりました
# JMH version: 1.21 # VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13 # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/bin/java # VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=55029:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 # Warmup: 10 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time (中略) Benchmark Mode Cnt Score Error Units FileExistsBenchmark.File_exists_found thrpt 5 377.495 ± 163.927 ops/ms FileExistsBenchmark.File_exists_not_found thrpt 5 484.295 ± 74.178 ops/ms FileExistsBenchmark.Files_exists_found thrpt 5 871.356 ± 46.111 ops/ms FileExistsBenchmark.Files_exists_not_found thrpt 5 91.218 ± 56.958 ops/ms
結果は上から順に以下の実行パターンのスループットを表しています(以降の記載でも同様です)
java.io.File#exists
+ 対象のファイルが存在するjava.io.File#exists
+ 対象のファイルが存在しないjava.nio.Files#exists
+ 対象のファイルが存在するjava.nio.Files#exists
+ 対象のファイルが存在しない
結果から一目瞭然ですが、「java.nio.Files#exists
+ 対象のファイルが存在しない」のパターンの性能が出ていません。なんてこったい。
macOS + Java 11 でのベンチマーク
このバグは、Java 9 時点で解消されてるらしいです。それならばJava 11でも問題ないはず!ということで、
macOS + Java 11(AdoptOpenJDK) でのベンチマークもとってみました。結果は以下の通りとなりました。
# JMH version: 1.21 # VM version: JDK 11.0.1, OpenJDK 64-Bit Server VM, 11.0.1+13 # VM invoker: /Library/Java/JavaVirtualMachines/adoptopenjdk-11.0.1.jdk/Contents/Home/bin/java # VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56002:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 # Warmup: 10 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time (中略) Benchmark Mode Cnt Score Error Units FileExistsBenchmark.File_exists_found thrpt 5 396.421 ± 40.107 ops/ms FileExistsBenchmark.File_exists_not_found thrpt 5 484.622 ± 20.577 ops/ms FileExistsBenchmark.Files_exists_found thrpt 5 880.723 ± 17.791 ops/ms FileExistsBenchmark.Files_exists_not_found thrpt 5 907.397 ± 26.587 ops/ms
こちらも結果から一目瞭然ですが、「java.nio.Files#exists
+ 対象のファイルが存在しない」のパターンの性能がちゃんと出ています。
というか、めっちゃ早くなってるじゃないですか笑
macOSでやったついでにWindows環境でも以下試してみました。
今回、macOSとWindowsで端末環境が異なる(別マシン)なので、各OS間で結果(絶対値)を直接的に比較することはできません。
Windows + Java 8 でのベンチマーク
Windows + Java 8 でのベンチマークは以下の通りとなりました
# JMH version: 1.21 # VM version: JDK 1.8.0_191, Java HotSpot(TM) 64-Bit Server VM, 25.191-b12 # VM invoker: C:\Program Files\Java\jdk1.8.0_191\jre\bin\java.exe # VM options: -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.5\lib\idea_rt.jar=51996:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.5\bin -Dfile.encoding=UTF-8 # Warmup: 10 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time (中略) Benchmark Mode Cnt Score Error Units FileExistsBenchmark.File_exists_found thrpt 5 41.620 ± 4.666 ops/ms FileExistsBenchmark.File_exists_not_found thrpt 5 89.797 ± 15.949 ops/ms FileExistsBenchmark.Files_exists_found thrpt 5 14.770 ± 2.707 ops/ms FileExistsBenchmark.Files_exists_not_found thrpt 5 18.481 ± 0.886 ops/ms
java.nio.Files#exists
の結果が、対象ファイルが存在してても、存在しなくても性能でないという結果になりました。
Windows + Java 11 でのベンチマーク
Windows + Java 11 でのベンチマークは以下の通りとなりました
# JMH version: 1.21 # VM version: JDK 11.0.1, OpenJDK 64-Bit Server VM, 11.0.1+13 # VM invoker: C:\Program Files\Java\OpenJDK11U-jdk_x64_hotspot_11.0.1_13\jdk-11.0.1_13\bin\java.exe # VM options: -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.5\lib\idea_rt.jar=49828:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.5\bin -Dfile.encoding=UTF-8 # Warmup: 10 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time (中略) Benchmark Mode Cnt Score Error Units FileExistsBenchmark.File_exists_found thrpt 5 44.025 ± 0.957 ops/ms FileExistsBenchmark.File_exists_not_found thrpt 5 86.267 ± 16.522 ops/ms FileExistsBenchmark.Files_exists_found thrpt 5 14.424 ± 1.863 ops/ms FileExistsBenchmark.Files_exists_not_found thrpt 5 19.877 ± 1.545 ops/ms
あれ?・・・速くなってない。java.nio.Files#exists
の結果が、全然速くなってない。
端末のせい?なんかミスったか???謎だ・・・。Windowsだとこういう仕様??
まとめ
macOS環境では、Java 8 では、「java.nio.Files#exists
+ 対象のファイルが存在しない」パターンが性能が出ないことが明確になりました。
というわけで、Java 8 環境では java.io.File#exists
を使った方がいい場合もあるかもしれません。
また、Java 11 では、java.nio.Files#exists
は性能が出ており、改善されていることが分かりました。
Windows環境の結果は謎だ・・・。