Files.newOutputStream(path, CREATE_NEW, DELETE_ON_CLOSE)无法写入文件。

4

I have the following code:

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;

public class Test {
    public static void main(String[] args) throws IOException {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get("test"), CREATE_NEW, DELETE_ON_CLOSE)) {
            outputStream.write(123);
            outputStream.flush();
            System.out.println("done");
        }
    }
}

我在调用System.out.println时设置了断点,并检查了我的工作目录。但是没有名为test的文件。为什么输出流没有将内容写入文件中呢?


嗯...对我来说可以(使用Thread.sleep而不是断点)。你在哪个操作系统上运行? - Jon Skeet
如果我也删除DELETE_ON_CLOSE,那么在OSX上它可以正常工作。 - Max
我认为的情况是,基于Unix的操作系统会立即删除文件,因为带有打开文件句柄的已删除文件仍然可以被访问。不过,这似乎违反了API的规定。 - Max
是的,我会在Linux上尝试并查看是否能够复现。(顺便说一下,如果您提供了一个包括导入的完整应用程序,那么对于人们来说更容易复现这个问题。) - Jon Skeet
我在Linux上遇到了相同的问题,@Max-DELETE_ON_CLOSE文档确实指出:“关于何时以及如何删除文件的许多细节是实现特定的,因此未予规定。” - Jonny Henly
显示剩余5条评论
1个回答

7
"The reason is that on Linux, you can delete a file from a directory even if the file is open (assuming appropriate permissions). This is not possible under Windows. This information is sourced from sun.nio.fs.UnixChannelFactory. If the 'delete on close' flag is set, the file will be unlinked immediately. However, it should be noted that the specification acknowledges that an implementation cannot guarantee to unlink the correct file when replaced by an attacker after it is opened. If you modify your code as suggested..."
for (int i = 0; i < 10; i++) {
    outputStream.write(123);
    outputStream.flush();
    System.out.println("flush...");
    Thread.sleep(10_000);
}

你能看到文件已经打开但已被删除。
# assumed that the code write to Paths.get("/tmp/test")
lsof | grep "/tmp/test"
...  /tmp/test (deleted)

编辑 如果您只想确保在应用程序退出时删除临时文件,请查看下面的代码片段。

import java.io.File;
import java.io.OutputStream;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("/tmp/test");
        file.deleteOnExit();
        System.out.println("tempFile = " + tempFile);
        try (OutputStream outputStream = Files.newOutputStream(file.toPath(),
                CREATE_NEW)) {
            outputStream.write(123);
            outputStream.flush();
            System.out.println("done");
        }
        System.out.printf("%s exists: %s%n", file, file.exists());
    }
}

文件/tmp/test将在应用程序完成时被删除。
输出(此时文件仍然存在)。
/tmp/test exists: true

如果你现在在控制台上检查
$ ls /tmp/test
ls: cannot access '/tmp/test': No such file or directory

如果您并不关心文件名,可以考虑使用随机生成的文件名。
File tempFile = File.createTempFile("foo_", ".tmp", new File("/tmp"));

编辑 另一个解决方案可能是:

  1. 创建文件(最好使用随机临时文件名)
  2. 打开 InputStream
  3. 使用 DELETE_ON_CLOSE 打开 OutputStream

按照这个顺序进行操作,它将按照您的预期工作。

以下是有效的代码片段。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;

public class DeleteOnClose {

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("/tmp/test");
        System.out.println("before create: " + Files.exists(path));
        Files.createFile(path);
        System.out.println("after create: " + Files.exists(path));
        try (InputStream in = Files.newInputStream(path, READ);
                OutputStream out = Files.newOutputStream(path, APPEND, 
                        DELETE_ON_CLOSE)) {
            out.write("Hello file!".getBytes(UTF_8));
            out.flush();

            for (int c = in.read(); c >= 0; c = in.read()) {
                System.out.print((char) c);
            }
            System.out.println();
        }
        System.out.println("after close: " + Files.exists(path));
    }
}

输出

before create: false
after create: true
Hello file!
after close: false

1
deleteOnExit 对我来说不起作用,因为这是一个长时间运行的进程(服务)。使用 deleteOnExit 可能会导致磁盘在 JVM 退出之前变满。 - Max
@Max,你能提供关于这个文件如何使用的更多信息吗?目前你只是创建了一个“只写”文件,在关闭时删除。在当前情况下似乎没有意义。是否有另一个进程/任务从该文件中读取?你能否提供一段代码片段,说明你想如何从该文件中读取数据? - SubOptimal
当然可以。我正在REST服务中接受一个文件上传。最终,我将把这个文件作为流传递给另一个服务,但是我需要先评估上传文件的内容。考虑到文件非常大,我不想将其全部存储在内存中。现在,我只是在finally块中删除该文件。这很好,只是似乎DELETE_ON_CLOSE更合适,但显然不是。 - Max
@Max,请查看新添加的示例。它可以使用DELETE_ON_CLOSE选项。 - SubOptimal
有趣的想法!谢谢。 - Max

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