Pertanyaan Perilaku ganjil saat menghapus File dengan Files.delete ()


Harap perhatikan contoh kelas Java berikut (pom.xml di bawah):

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());
        }
    }
}

Saya menulis ke FileOutputStream dan mencoba untuk menghapus file sesudahnya tanpa menutup Stream terlebih dahulu. Ini adalah masalah asli saya, dan tentu saja salah, tetapi itu mengarah pada beberapa pengamatan aneh.

Ketika Anda menjalankan Metode utama pada Windows 7, ia menghasilkan output berikut:

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
  • Mengapa panggilan pertama ke Files.delete () tidak melempar pengecualian?
  • Mengapa panggilan berikut ke Files.exist () mengembalikan false?
  • Mengapa tidak mungkin membuat file baru?

Mengenai pertanyaan terakhir saya perhatikan bahwa file tersebut masih terlihat di Explorer ketika Anda berhenti di breakpoint 1. Ketika Anda mengakhiri JVM kemudian, file tersebut akan dihapus pula. Setelah menutup aliran, deleteAndCheck () berfungsi seperti yang diharapkan.

Sepertinya saya bahwa penghapusan tidak disebarkan ke OS sebelum menutup aliran dan API File tidak mencerminkan ini dengan benar.

Bisakah seseorang menjelaskan dengan tepat apa yang terjadi di sini?

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>

Pembaruan untuk klarifikasi

File menghilang di Windows explorer, jika aliran ditutup DAN Files.delete () dipanggil - pemicu operasi terakhir -, atau jika File.delete () dipanggil tanpa menutup aliran dan JVM diakhiri.


18
2017-07-24 09:31


asal


Jawaban:


Bisakah Anda menghapus file terbuka?

Ini sangat valid untuk menghapus entri direktori file ketika file dibuka. Di Unix ini adalah semantik default dan Windows berperilaku sama selama FILE_SHARE_DELETE diatur pada semua file menangani terbuka untuk file itu.

[Edit: Terima kasih untuk @couling untuk diskusi dan koreksi]

Namun, ada sedikit perbedaan: Unix menghapus file nama segera, saat Windows menghapus nama file hanya ketika pegangan terakhir ditutup. Namun, mencegah Anda dari membuka file dengan nama yang sama sampai pegangan terakhir ke file (dihapus) ditutup.

Ayo cari ...

Pada kedua sistem, bagaimanapun, menghapus file tidak selalu membuat file pergi, itu masih menempati ruang pada disk selama masih ada pegangan terbuka untuk itu. Ruang yang ditempati oleh file hanya dirilis ketika pegangan terbuka terakhir ditutup.

Excursion: Windows

Bahwa perlu untuk menentukan bendera di Windows membuatnya tampak bagi kebanyakan orang bahwa Windows tidak dapat menghapus file terbuka, tetapi itu sebenarnya tidak benar. Itu hanya itu default tingkah laku.

Situasi ini tidak benar-benar diperbaiki oleh fakta bahwa dokumentasi Windows API tampaknya (sengaja?) Tidak jelas pada proses:

CreateFile():

Mengaktifkan operasi terbuka berikutnya pada file atau perangkat untuk meminta akses hapus.

Jika tidak, proses lain tidak dapat membuka file atau perangkat jika mereka meminta akses hapus.

Jika bendera ini tidak ditentukan, tetapi file atau perangkat telah dibuka untuk menghapus akses, fungsi gagal.   Catatan Hapus akses memungkinkan baik menghapus dan mengganti nama operasi.

DeleteFile():

Fungsi DeleteFile menandai file untuk dihapus dari dekat. Oleh karena itu, penghapusan file tidak terjadi sampai pegangan terakhir ke file ditutup. Panggilan selanjutnya ke CreateFile untuk membuka file gagal dengan ERROR_ACCESS_DENIED.

Memiliki pegangan terbuka untuk file tanpa nama adalah salah satu metode yang paling umum untuk membuat file temporer yang tidak disebutkan namanya: Buat file baru, buka file tersebut, hapus file tersebut. Anda sekarang memiliki pegangan untuk file yang tidak dapat dibuka oleh siapa pun. Pada Unix, nama file benar-benar hilang dan pada Windows Anda tidak dapat membuka file dengan nama yang sama.

Pertanyaannya sekarang:

Apakah Files.newOutputStream () set FILE_SHARE_DELETE?

Melihat ke sumber, Anda bisa melihatnya shareDelete memang default untuk true. Satu-satunya cara untuk meresetnya adalah menggunakan yang tidak standar ExtendedOpenOption  NOSHARE_DELETE.

Jadi ya, Anda dapat menghapus file yang dibuka di Java kecuali mereka terkunci secara eksplisit.

Mengapa saya tidak bisa membuat ulang file yang dihapus?

Jawabannya tersembunyi dalam dokumentasi DeleteFile() di atas: File hanya ditandai untuk dihapus, file tersebut masih ada. Di Windows, Anda tidak dapat membuat file dengan nama dari sebuah file yang ditandai untuk dihapus sampai file tersebut tepat dihapus, yaitu semua pegangan ke file ditutup.

Kemungkinan kebingungan mencampur nama penghapusan dan penghapusan file yang sebenarnya mungkin adalah mengapa Windows tidak mengizinkan penghapusan file terbuka secara default di tempat pertama.

Kenapa Files.exists() kembali false?

Files.exists() di ujung mendalam pada Windows membuka file itu di beberapa titik dan kita sudah tahu bahwa kita tidak dapat membuka kembali file yang dihapus-tetapi-masih-terbuka di Windows.

Secara rinci: panggilan kode Java FileSystemProvider.checkAccess()) tanpa argumen, panggilan mana WindowsFileSystemProvider.checkReadAccess() yang langsung mencoba membuka file dan karenanya gagal. Dari apa yang bisa saya katakan, ini adalah jalan yang diambil saat Anda menelepon Files.exist().

Ada juga jalur kode lain yang memanggil GetFileAttributeEx() untuk mengambil atribut file. Sekali lagi, tidak didokumentasikan apa yang terjadi ketika Anda mencoba untuk mengambil atribut dari suatu dihapus tetapi belum dihapus file, tetapi memang, Anda tidak dapat mengambil atribut file dari suatu file ditandai untuk dihapus.

Tebak, saya akan mengatakan itu GetFileAttributeEx() panggilan GetFileInformationByHandle() di beberapa titik, yang tidak akan pernah bisa karena tidak bisa mendapatkan pegangan file di tempat pertama.

Jadi memang, setelah DeleteFile() file tersebut hilang untuk sebagian besar tujuan praktis. Itu masih memiliki nama, namun, muncul di daftar direktori dan Anda tidak dapat membuka file dengan nama yang sama sampai file aslinya menutup semua pegangannya.

Perilaku ini kurang lebih konsisten, karena menggunakan GetFileAttributes() untuk memeriksa apakah file yang ada adalah benar-benar sebuah file aksesibilitas file periksa, yang ditafsirkan sebagai file ada. FindFirstFile() (digunakan oleh Windows Explorer untuk menentukan daftar file) menemukan file nama-nama tapi tidak memberitahumu apa-apa aksesibilitas nama-nama itu.

Selamat datang di beberapa lingkaran aneh di kepalamu.


17
2017-07-24 10:28



Jika File.delete tidak membuang exception berarti itu menghapus file. File.delete javadoc mengatakan bahwa "pada beberapa sistem operasi mungkin tidak dapat menghapus file ketika terbuka dan digunakan oleh mesin virtual Java ini atau program lain".


2
2017-07-24 09:57