web-dev-qa-db-ja.com

Files.delete()でファイルを削除するときの奇妙な動作

次の例を検討してくださいJavaクラス(以下のpom.xml)):

package test.filedelete;

import Java.io.ByteArrayInputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.nio.file.Files;
import Java.nio.file.NoSuchFileException;
import Java.nio.file.Path;

import org.Apache.commons.io.IOUtils;

public class Main
{
    public static void main(String[] args) throws IOException
    {
        byte[] bytes = "testtesttesttesttesttesttesttesttesttest".getBytes();
        InputStream is = new ByteArrayInputStream(bytes);

        Path tempFileToBeDeleted = Files.createTempFile("test", "");
        OutputStream os = Files.newOutputStream(tempFileToBeDeleted);
        IOUtils.copy(is, os);

        deleteAndCheck(tempFileToBeDeleted);

        // breakpoint 1
        System.out.println("\nClosing stream\n");

        os.close();

        deleteAndCheck(tempFileToBeDeleted);
    }

    private static void deleteAndCheck(Path file) throws IOException
    {
        System.out.println("Deleting file: " + file);
        try
        {
            Files.delete(file);
        }
        catch (NoSuchFileException e)
        {
            System.out.println("No such file");
        }
        System.out.println("File really deleted: " + !Files.exists(file));

        System.out.println("Recreating deleted file ...");
        try
        {
            Files.createFile(file);
            System.out.println("Recreation successful");
        }
        catch (IOException e)
        {
            System.out.println("Recreation not possible, exception: " + e.getClass().getName());
        }
    }
}

FileOutputStreamに書き込み、最初にストリームを閉じずに後でファイルを削除しようとします。これは私の元々の問題であり、もちろん間違っていましたが、奇妙な観察につながりました。

Windows 7でメインメソッドを実行すると、次の出力が生成されます。

Deleting file: C:\Users\MSCHAE~1\AppData\Local\Temp\test6100073603559201768
File really deleted: true
Recreating deleted file ...
Recreation not possible, exception: Java.nio.file.AccessDeniedException

Closing stream

Deleting file: C:\Users\MSCHAE~1\AppData\Local\Temp\test6100073603559201768
No such file
File really deleted: true
Recreating deleted file ...
Recreation successful
  • Files.delete()の最初の呼び出しで例外がスローされないのはなぜですか?
  • 次のFiles.exist()の呼び出しがfalseを返すのはなぜですか?
  • なぜファイルを新しく作成できないのですか?

最後の質問に関して、ブレークポイント1で停止してもファイルがエクスプローラーに表示されていることに気付きました。JVMを終了すると、ファイルはとにかく削除されます。ストリームを閉じた後、deleteAndCheck()は期待どおりに機能します。

ストリームを閉じる前に削除がOSに伝播されず、Files APIがこれを正しく反映していないように思えます。

誰かがここで何が起こっているのか正確に説明できますか?

pom.xml

<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test</groupId>
    <artifactId>filedelete</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
    </dependencies>
</project>

説明の更新

ストリームが閉じていて、Files.delete()が呼び出された場合(最後の操作がトリガーされた場合)、またはストリームを閉じずにFiles.delete()が呼び出され、JVMが終了した場合、ファイルはWindowsエクスプローラーに表示されなくなります。

22
Mark Schäfer

開いているファイルを削除できますか?

ファイルを開いたときにファイルのディレクトリエントリを削除することは完全に有効です。 Unixでは、これがデフォルトのセマンティクスであり、Windowsは _FILE_SHARE_DELETE_ がそのファイルに開かれているすべてのファイルハンドルに設定されている限り、同様に動作します。

[編集:ディスカッションと修正について@coulingに感謝]

ただし、わずかな違いがあります。Unixはファイルnameをすぐに削除します一方で、Windowsは、最後のハンドルが閉じられたときにのみファイル名を削除します。ただし、(削除された)ファイルへの最後のハンドルが閉じられるまで、同じ名前のファイルを開くことはできません。

フィギュア行く...

ただし、どちらのシステムでも、ファイルを削除しても必ずしもファイルがなくなるわけではありません。ファイルへのオープンハンドルが存在する限り、ディスク上の領域を占有します。ファイルによって占有されているスペースは、最後に開いたハンドルが閉じられたときにのみ解放されます。

エクスカーション:Windows

Windowsでフラグを指定する必要があるということは、ほとんどの人にとってWindowsが開いているファイルを削除できないように思えますが、実際にはそうではありません。これはdefaultの動作です。

CreateFile()

ファイルまたはデバイスでの後続のオープン操作を有効にして、削除アクセスを要求します。

そうしないと、他のプロセスが削除アクセスを要求した場合、ファイルまたはデバイスを開くことができません。

このフラグが指定されていないが、ファイルまたはデバイスが削除アクセスのために開かれている場合、関数は失敗します。注削除アクセスでは、削除操作と名前変更操作の両方が可能です。

DeleteFile()

DeleteFile関数は、閉じるときにファイルに削除のマークを付けます。したがって、ファイルの最後のハンドルが閉じられるまで、ファイルの削除は行われません。その後、CreateFileを呼び出してファイルを開くと、ERROR_ACCESS_DENIEDで失敗します。

名前のないファイルへのオープンハンドルを持つことは、名前のない一時ファイルを作成する最も一般的な方法の1つです。新しいファイルを作成して開き、ファイルを削除します。これで、他のユーザーが開けないファイルへのハンドルができました。 Unixではファイル名は完全になくなっており、Windowsでは同じ名前のファイルを開くことができません。

質問は今です:

Does Files.newOutputStream() set _FILE_SHARE_DELETE_?

ソース を見ると、shareDeleteがデフォルトでtrueになっていることがわかります。これをリセットする唯一の方法は、非標準のExtendedOpenOption_NOSHARE_DELETE_ を使用することです。

つまり、明示的にロックされていない限り、Javaで開いているファイルを削除できます。

削除したファイルを再作成できないのはなぜですか?

これに対する答えは、上記のDeleteFile()のドキュメントに隠されています。ファイルには削除のマークが付けられているだけで、ファイルは残っています。 Windowsでは、ファイルが正しく削除されるまで、つまりすべて削除されるようにマークされたファイルのnameでファイルを作成できません。ファイルへのハンドルが閉じています。

名前の削除と実際のファイルの削除が混同している可能性があるため、おそらくWindowsは最初からデフォルトで開いているファイルの削除を許可していません。

Files.exists()falseを返す理由

WindowsのディープエンドにあるFiles.exists()は、ある時点でそのファイルを開きますが、削除されたがまだ開いているファイルをWindowsで再び開くことはできません

詳細:Javaコードは FileSystemProvider.checkAccess() )を引数なしで呼び出し、 WindowsFileSystemProvider.checkReadAccess() を呼び出しますすぐにファイルを開こうとするため、失敗します。私が知ることができることから、これはFiles.exist()を呼び出すときに取られるパスです。

ファイル属性を取得するために GetFileAttributeEx() を呼び出す別のコードパスもあります。繰り返しますが、削除されたがまだ削除されていないファイルの属性を取得しようとしたときに何が起こるかは文書化されていませんが、実際には、ファイルのファイル属性を取得できません削除用にマークされています

推測すると、私はGetFileAttributeEx()GetFileInformationByHandle() を呼び出すと思いますが、最初にファイルハンドルを取得できないため、この関数には到達しません。 。

実際、DeleteFile()を実行すると、ファイルはほとんどの実用的な目的で削除されます。それでも名前は残っていますが、ディレクトリリストには表示され、元のファイルのすべてのハンドルが閉じられるまで、同じ名前のファイルを開くことはできません。

GetFileAttributes()を使用してファイルが存在するかどうかを確認するのは、実際にはファイルのアクセス可能性チェックであり、ファイルが存在しますFindFirstFile() (Windowsエクスプローラーでファイルリストを決定するために使用)はファイルnamesを検索しますが、名前のアクセシビリティ

あなたの頭の中のいくつかの奇妙なループへようこそ。

26
dhke

Files.deleteが例外をスローしなかった場合は、ファイルが削除されたことを意味します。 Files.delete javadocによると、「一部のオペレーティングシステムでは、ファイルが開いていて、このJava仮想マシンまたは他のプログラム)で使用されている場合、ファイルを削除できない場合があります」。

2