この前、実際の業務でちょっと困ったことがおきました。
ちょっと前に作ったシステムなのですが、“ある特定のファイルが削除されない”という現象です。
今回は、実際におきた現象をもとに、「java.io.File」と「java.nio.file.Files」の違いを実際のプログラムを使って紹介していきます。
実際に起きた現象自体は解消されたのですが、一番困ったのは調査です。
原因は、削除するファイルが他プロセスにつかまれていることが原因だったのですが、その原因がすぐにはわからなかったです。
「java.io.File」を使ってファイル削除していたのですが、他プロセスが削除対象ファイルをつかんでいる場合は、返り値で失敗(false)を返却しているのみで、例外が発生しないです。
なので、削除されない原因がすぐにはわかりませんでした。
そのため、「java.io.File」ではなく「java.nio.file.Files」に切り替えて例外をログ出力するようにすることで、ようやく原因がわかりました。
サンプルプログラム
二つのプログラムで試してみます。
「java.io.File」で削除するプログラムは以下。
import java.io.IOException; import java.io.File; public class FileDelete { public static void main(String[] args){ System.out.print("start: main\r\n"); boolean isDone = false; try { File file = new File("C:\\Temp\\file.txt"); isDone = file.delete(); } catch (Exception e) { e.printStackTrace(); } finally { System.out.print("isDone=" + isDone + "\r\n"); } System.out.print("end: main\r\n"); } }
「java.nio.file.Files」で削除するプログラムは以下。
import java.io.IOException; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; public class FileDeleteIO { public static void main(String[] args){ System.out.print("start: main\r\n"); try { Files.delete((Paths.get("C:\\Temp\\file.txt"))); } catch (Exception e) { e.printStackTrace(); } finally { System.out.print("finally\r\n"); } System.out.print("end: main\r\n"); } }
「java.io.File」は削除結果を返却してくれるので削除結果を標準出力していますが、「java.nio.file.Files」の方は削除結果を返却しないので何も標準出力していません。
この2つのプログラムを使用して、「java.io.File」と「java.nio.file.Files」の違いを検証します。
ファイルの削除について挙動の違いを検証します
以下の3パターンです。
- 削除対象ファイルが存在しない場合
- 削除対象ファイルに対して削除権限がない場合
- 削除対象ファイルが他プロセスに開かれている場合
存在しないファイルを削除
削除対象のファイルが存在しない場合の挙動を試してみます。
「java.io.File」の結果は以下になります。
start: main isDone=false end: main
「java.io.File」の結果がfalse(失敗)を返しました。
「java.nio.file.Files」の結果は以下になります。
start: main java.nio.file.NoSuchFileException: C:\Temp\file.txt at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:85) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108) at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274) at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105) at java.base/java.nio.file.Files.delete(Files.java:1144) at FileDeleteIO.main(FileDeleteIO.java:11) finally end: main
例外が発生しました。
「NoSuchFileException」というファイルが存在しません、という例外です。
この例外を嫌う場合は「delete」ではなく「deleteIfExists」を使えばいいです。
削除権限がないファイルを削除
削除対象のファイルについて削除権限が存在しない場合の挙動を試してみます。
「java.io.File」の結果は以下になります。
start: main isDone=false end: main
「java.io.File」の結果がfalse(失敗)を返しました。
「java.nio.file.Files」の結果は以下になります。
start: main java.nio.file.AccessDeniedException: C:\Temp\file.txt at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:89) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108) at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274) at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105) at java.base/java.nio.file.Files.delete(Files.java:1144) at FileDeleteIO.main(FileDeleteIO.java:11) finally end: main
例外が発生しました。
「AccessDeniedException」という、ファイルへのアクセス権限が存在しません、という例外です。
他のプロセスが開いているファイルを削除
削除対象のファイルについて、他のプロセスがファイルを開いている場合の挙動について試してみます。
あえて他のプロセスがファイルをつかんでいる状態を発生させるために、簡単なプログラムを作りました。
import java.io.IOException; import java.io.FileOutputStream; import java.io.File; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public class FileLocker { public static void main(String[] args){ System.out.print("start: main\r\n"); // 削除対象ファイルを読み込み File file = new File("C:\\Temp\\file.txt"); FileChannel fc = null; FileLock lock = null; try { // ファイルを読み込み、ファイルをロック FileOutputStream fos = new FileOutputStream(file); fc = fos.getChannel(); lock = fc.tryLock(); // ロックに失敗した場合、例外をスロー if (lock == null) { throw new Exception("lock failer"); } // 1秒スリープ System.out.print("sleeping...\r\n"); Thread.sleep(60000); } catch (Exception e) { e.printStackTrace(); } finally { if (lock != null) { try { lock.release(); } catch (IOException ex) { ex.printStackTrace(); } } } System.out.print("end: main\r\n"); } }
このプログラムでファイルをつかんでいる状態を作り出して、ファイル削除をためしてみます。
「java.io.File」の結果は以下になります。
start: main isDone=false end: main
「java.io.File」の結果がfalse(失敗)を返しました。
「java.nio.file.Files」の結果は以下になります。
start: main java.nio.file.FileSystemException: C:\Temp\file.txt: プロセスはファイルにアクセスできません。別のプロセスが使用中です。 at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103) at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108) at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274) at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:105) at java.base/java.nio.file.Files.delete(Files.java:1144) at FileDeleteIO.main(FileDeleteIO.java:11) finally end: main
例外が発生しました。
「FileSystemException」という例外で、ファイルシステムとしてはこのファイルが他プロセスにロックされてるという例外です。
筆者が直面した問題がまさにこれでして、「java.io.File」だと「false」とだけ出力されるので、何か原因で削除されなかったのかがわからない。。。
「java.nio.file.Files」だと例外が出力されるので、原因についてある程度のあたりがつきます。
まとめ
「java.io.File」と「java.nio.file.Files」、どっちを使う方がいいのでしょうか?
実装するアプリケーションの特性にもよるので一概にはどちらがいい、とは言えないのですが、筆者は今後は極力「java.nio.file.Files」を使うようにしようと思っています。
理由はやはり、なにかしらエラー(削除失敗、など)が発生した場合の原因調査。
例外がトレースとして出力されるのとされないのでは、障害発生時の調査に掛かる時間が圧倒的に違います。
今後は「java.nio.file.Files」を使うようにしよう。。。
それではまた!