DELETE_ON_CLOSE的用处是什么?

9

互联网上有很多关于如何使用StandardOpenOption.DELETE_ON_CLOSE的示例,例如:

Files.write(myTempFile, ..., StandardOpenOption.DELETE_ON_CLOSE);

其他示例同样使用 Files.newOutputStream(..., StandardOpenOption.DELETE_ON_CLOSE)

我怀疑所有这些示例都可能存在缺陷。写入文件的目的是在某个时候你会读取它;否则,为什么要写它呢?但是,DELETE_ON_CLOSE是否会在你有机会读取文件之前删除文件呢?

如果您创建一个工作文件(用于处理太大而无法保存在内存中的大量数据),那么您不会使用允许读写访问的RandomAccessFile吗?但是,据我所见,RandomAccessFile没有提供指定DELETE_ON_CLOSE的选项。

那么,有人能向我展示DELETE_ON_CLOSE实际上有用吗?


2
我最近也在思考同样的问题。令人惊讶的是,Oracle针对“将java.io.File功能映射到java.nio.file”的文档建议使用DELETE_ON_CLOSE而不是旧的File.deleteOnExit方法,但它们并不相同! - daiscog
1
我能想到的唯一例子是,如果你需要测试文件的某个大小是否适合写入,但我认为这并没有太大用处。 - eis
1
@daiscog Oracle文档中说File.deleteOnExit已被“在createFile方法中指定的DELETE_ON_CLOSE选项所取代。”这是完全错误的。createFile方法并不接受DELETE_ON_CLOSE参数。 - Klitos Kyriacou
1
@KlitosKyriacou 确实。显然,Oracle对其文档的质量控制还有很大的提升空间。File.deleteOnExit方法在新的java.nio.file API中实际上没有合适的替代方法,这意味着在某些情况下转移到新的API是不可能的。 - daiscog
3个回答

3
首先,我同意你的看法,Files.write(myTempFile, ..., StandardOpenOption.DELETE_ON_CLOSE) 在这个例子中使用 DELETE_ON_CLOSE 是没有意义的。在互联网上进行了(不算太强烈的)搜索后,我唯一找到的一个显示出所提到用法的例子是你可能从中获取的(http://softwarecave.org/2014/02/05/create-temporary-files-and-directories-using-java-nio2/)。
这个选项不仅适用于 Files.write(...)API 很清楚地说明了:

该选项主要用于仅由 Java 虚拟机的单个实例使用的 工作文件。不建议在同时被其他实体并发打开的文件上使用此选项。

抱歉我不能给您一个有意义的简短示例,但是可以将这样的文件视为操作系统使用的交换文件/分区。在当前JVM需要临时将数据存储到磁盘上,并在关闭后数据不再使用的情况下。实际的例子是类似于JEE应用程序服务器可能会决定将一些实体序列化到磁盘上以释放内存。
编辑:也许以下(过度简化的代码)可以作为示例来演示原理。(因此请:没有人应该开始讨论这个“数据管理”可以使用固定的临时文件名进行处理,删除选项关闭后JVM会自动处理等等...)
在try-with-resource块中,由于某种原因,您需要将数据外部化(原因不是讨论的主题) 您可以随机读/写访问此外部化数据 此外部化数据仅在try-with-resource块内有用 通过使用StandardOpenOption.DELETE_ON_CLOSE选项,您无需自己处理使用后的删除,JVM会负责处理(限制和边缘情况在API中描述)。

.

static final int RECORD_LENGTH = 20;
static final String RECORD_FORMAT = "%-" + RECORD_LENGTH + "s";

// add exception handling, left out only for the example
public static void main(String[] args) throws Exception {
    EnumSet<StandardOpenOption> options = EnumSet.of(
            StandardOpenOption.CREATE,
            StandardOpenOption.WRITE,
            StandardOpenOption.READ,
            StandardOpenOption.DELETE_ON_CLOSE
    );

    Path file = Paths.get("/tmp/enternal_data.tmp");
    try (SeekableByteChannel sbc = Files.newByteChannel(file, options)) {

        // during your business processing the below two cases might happen
        // several times in random order

        // example of huge datastructure to externalize
        String[] sampleData = {"some", "huge", "datastructure"};
        for (int i = 0; i < sampleData.length; i++) {
            byte[] buffer = String.format(RECORD_FORMAT, sampleData[i])
                    .getBytes();
            ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
            sbc.position(i * RECORD_LENGTH);
            sbc.write(byteBuffer);
        }

        // example of processing which need the externalized data
        Random random = new Random();
        byte[] buffer = new byte[RECORD_LENGTH];
        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
        for (int i = 0; i < 10; i++) {
            sbc.position(RECORD_LENGTH * random.nextInt(sampleData.length));
            sbc.read(byteBuffer);
            byteBuffer.flip();
            System.out.printf("loop: %d  %s%n", i, new String(buffer));
        }
    }
}

我也读了那份API文档,但是它对我来说并没有太多意义。我唯一能想到的让DELETE_ON_CLOSE有用的方法是在打开OutputStream时指定它,然后在同一路径上打开一个InputStream。文件需要在OutputStream关闭之前被读取(否则文件将被删除)。所以你同时拥有了一个OutputStream和一个InputStream在同一路径上。这不会违反API文档中说的“当打开其他实体同时打开的文件时,不建议使用此选项。”吗? - Klitos Kyriacou
@KlitosKyriacou 我认为文档中的“其他实体”指的是独立的代码部分。我认为一个应用程序代码的单个部分控制输入和输出流不算。 - daiscog
@KlitosKyriacou 正如@daiscog所提到的,我认为它应该使用相同的通道进行读写。对于我来说,使用单独的Output-和InputStream将是一个不必要的情况。请参考我添加的示例以演示我的想法。 - SubOptimal

2
DELETE_ON_CLOSE是用于处理临时文件的。
如果您需要进行某些需要在文件中临时存储的操作,但不需要在当前执行之外使用该文件,则DELETE_ON_CLOSE是一个很好的解决方案。
例如,当您需要存储无法在内存中维护的信息时,例如因为它们太重,或者当您需要临时存储信息并且仅在稍后的某个时刻需要它们,并且不希望占用内存。
另请想象一种情况,其中进程需要很长时间才能完成。 您将信息存储在文件中,仅在稍后使用它们(也许是几分钟或几小时后)。 如果您不需要这些信息,则可以保证不使用内存。 DELETE_ON_CLOSE尝试在显式调用close()方法关闭文件时或JVM关闭之前自动删除该文件。

是的,我理解,但这样的文件有什么用处呢? - Klitos Kyriacou
我会添加一些例子。 - Davide Lorenzo MARINO
3
OP 提供的示例,Files.write() 说明:“当所有字节都被写入(或者抛出 I/O 错误或其他运行时异常)时,该方法确保文件已关闭。” 因此,我不认为有任何读取所写内容的方法。这使其甚至对于临时存储也没有用处,所以我看不到你回答的意义。 - eis
1
以您的例子为例:“暂时存储信息,只在稍后需要它们”和“将信息存储在文件中,稍后再使用它们”。如果您需要使用刚刚写入文件的信息,该如何访问这些信息?该文件是只写的。这就是我提出问题的全部意义。您能展示一些代码吗? - Klitos Kyriacou
还有一些技巧可以使用旧的deleteOnExit行为。我会搜索链接并向您展示。 - Davide Lorenzo MARINO
显示剩余7条评论

1

这里有两种可能的使用方式:

1. 在调用Files.newByteChannel

此方法返回一个SeekableByteChannel,适用于读写操作,其中当前位置可以修改。

在需要将某些数据存储到内存之外以进行读/写访问且不需要在应用程序关闭后保留的情况下,似乎非常有用。

2. 写入文件,读取,删除:

使用任意文本文件的示例:

Path p = Paths.get("C:\\test", "foo.txt");
System.out.println(Files.exists(p));
try {
    Files.createFile(p);
    System.out.println(Files.exists(p));
    try (BufferedWriter out = Files.newBufferedWriter(p, Charset.defaultCharset(), StandardOpenOption.DELETE_ON_CLOSE)) {
        out.append("Hello, World!");
        out.flush();
        try (BufferedReader in = Files.newBufferedReader(p, Charset.defaultCharset())) {
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
} catch (IOException ex) {
    ex.printStackTrace();
}
System.out.println(Files.exists(p));

这会按预期输出:
false
true
Hello, World!
false

这个例子显然是微不足道的,但我想在很多情况下,这种方法可能会派上用场。
但是,我仍然认为旧的File.deleteOnExit方法可能更可取,因为您不需要在文件的任何读操作期间保持输出流处于打开状态。

我喜欢你使用newByteChannel的解决方案。第二个选项,使用newBufferedReader,在Windows上可以工作,但我不确定它是否适用于Linux。今晚回家后我会检查一下。你看,在Linux上,当你指定DELETE_ON_CLOSE时,文件是打开并删除的,因此在文件系统中没有指向该文件的目录条目,因此调用newBufferedReader可能无法从指定路径找到该文件,即使该文件仍然存在。 - Klitos Kyriacou
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - daiscog
转念一想,我猜在读取文件时指定DELETE_ON_CLOSE可能是有用的。 - Klitos Kyriacou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接