尝试使用资源处理临时文件

Handle temporary file in try with resources

对于我的应用程序,我必须编写一个方法,将 InputStream 作为参数,将内容写入临时文件,执行一些操作,最后删除临时文件。

这是我目前拥有的:

public void myMethod(InputStream in, String name) {
    //...
    Path path = Paths.get("./tmp/benchmarks/" + name + ".zip")

    try {
        Files.copy(in, path);
        //operations...
    } catch (IOException e) {
        //error handling for copy...
    } finally {
        try {
            Files.delete(path));
       } catch (IOException e) {
           //error handling for delete...
       }
    }
    //...
}

它完成了工作,但它看起来也很丑。我想知道是否有某种方法可以使用 try-with-resources 来更优雅地处理这个问题。 有可能吗?

更新: 我在十分钟内写了一个即时解决方案。它看起来像这样:

public class TemporaryFileHandler implements AutoCloseable {

    private File file;

    public TemporaryFileHandler(final InputStream in, final Path path) throws IOException {
        Files.copy(in, path);
        this.file = new File(path.toString());
    }

    public File getFile() { return file; }

    @Override
    public void close() throws IOException {
        Files.delete(file.toPath());
    }
}

我敢肯定它不是最好的,但它现在可以完成工作。如果有人对如何以任何方式改进这一点有任何建议,我们非常欢迎。

Try-with-resource 只是在实现接口 java.lang.AutoCloseable 的 类 上调用关闭方法。没有什么可以阻止您创建实现 AutoCloseable 并在调用 close() 时删除自身的 File 实现。

您也可以在文件上调用 deleteOnExit() 让 JVM 在文件退出时将其删除。仅当您可以等到 JVM 完成删除临时文件时,这才适用。对于像 Java webapp 这样的长 运行 JVM,这可能不是一个好主意。

Files.createTempFile 允许您在 JVM 的默认临时目录中创建一个临时文件。这并不意味着文件会自动删除或使用 File.deleteOnExit() 标记为删除。开发者负责管理临时文件的生命周期。

文件名是安全漏洞的载体。仅使用名称在 UI 中显示以进行验证和反馈。不要使用不受信任的用户输入文件名。

使用 java.io.File or java.nio.file.Path 无法使用 try-with-resources 管理文件的生命周期。 InputStream 可以使用 try-with-resources 进行管理。

public void myMethod(InputStream in) {
    Path path = null;

    try (InputStream stream = in) {
        path = Files.createTempFile(null, ".zip");
        Files.copy(stream, path);
    } catch (IOException e) {
        //error handling for copy...
    } finally {
        if (path != null) {
            try {
                Files.delete(path));
           } catch (IOException e) {
               //error handling for delete...
           }
        }
    }
}

我想像

这样的小帮手/包装器
public class AutoDeletingTempFile implements AutoCloseable {

    private final Path file;

    public AutoDeletingTempFile() throws IOException {
        file = Files.createTempFile(null, null);
    }

    public Path getFile() {
        return file;
    }

    @Override
    public void close() throws IOException {
        Files.deleteIfExists(file);
    }
}

它被关闭并删除它包装的文件你得到一个漂亮而简短的语法:

public void myMethod(InputStream in, String name) {
    try (AutoDeletingTempFile wrapper = new AutoDeletingTempFile()) {
        //Files.copy(in, wrapper.getFile());
        //operations...
    } catch (IOException e) {
        //error handling for copy...
        // + temp file creation
    }
}

或通过 lambdas

实现的简洁 Closable
public void myMethod(InputStream in, Path existingFile, String name) {
    try (Closeable closable = () -> Files.deleteIfExists(existingFile)) {
        // ...
    } catch (IOException e) {
        // 
    }
}

我有一个类似的问题,就是临时 ZIP 文件没有被删除。我的假设是在代码试图删除临时文件之前输出流没有被关闭。

我的解决方案使用嵌套尝试,虽然不那么优雅,但它应该保证事先关闭流。

之前

File out = // get file;

try(
    FileOutputStream fos = new FileOutputStream(out);
    ZipOutputStream zos =  new ZipOutputStream(fos);
){
    // Create ZIP file and deliver to client using HTTPServletResponse
}
finally{
    if (out != null){
        out.delete();
    }
}

之后

File out = // get file;

try{
    try(
        FileOutputStream fos = new FileOutputStream(out);
        ZipOutputStream zos =  new ZipOutputStream(fos);
    ){
        // Create ZIP file and deliver to client using HTTPServletResponse
    }
}
finally{
    if (out != null){
        out.delete();
    }
}

您可以在 java 8:

中执行类似的操作
Path path = Files.createTempFile("temp-", ".tmp");
try (Closeable onClose = () -> Files.delete(path)) {
    ...
}

但这实际上是一样的:

Path path = Files.createTempFile("temp-", ".tmp");
try {
    ...
} finally {
    Files.delete(path);
}