即使dest文件已经存在,如何在Java中原子地重命名文件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/595631/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:39:27  来源:igfitidea点击:

How to atomically rename a file in Java, even if the dest file already exists?

javafilecross-platform

提问by Sébastien RoccaSerra

I have a cluster of machines, each running a Java app.

我有一组机器,每台机器都运行一个 Java 应用程序。

These Java apps need to access a unique resource.txtfile concurently.

这些 Java 应用程序需要同时访问唯一的resource.txt文件。

I need to atomically rename a temp.txtfile to resource.txtin Java, even if resource.txtalready exist.

我需要在 Java 中原子地将temp.txt文件重命名为resource.txt,即使resource.txt已经存在。

Deleting resource.txtand renaming temp.txtdoesn't work, as it's not atomic (it creates a small timeframe where resource.txtdoesn't exist).

删除resource.txt和重命名temp.txt不起作用,因为它不是原子的(它创建了一个resource.txt不存在的小时间范围)。

And it should be cross-platform...

它应该是跨平台的......

Thanks !

谢谢 !

回答by Stephen

On Linux (and I believe Solaris and other UNIX operating systems), Java's File.renameTo() method will overwrite the destination file if it exists, but this is not the case under Windows.

在 Linux(我相信 Solaris 和其他 UNIX 操作系统)上,Java 的 File.renameTo() 方法将覆盖目标文件(如果存在),但在 Windows 下情况并非如此。

To be cross platform, I think you'd have to use file locking on resource.txt and then overwrite the data.

要跨平台,我认为您必须在 resource.txt 上使用文件锁定,然后覆盖数据。

The behavior of the file lock is platform-dependent. On some platforms, the file lock is advisory, which means that unless an application checks for a file lock, it will not be prevented from accessing the file. On other platforms, the file lock is mandatory, which means that a file lock prevents any application from accessing the file.

文件锁的行为是平台相关的。在某些平台上,文件锁是建议性的,这意味着除非应用程序检查文件锁,否则不会阻止它访问文件。在其他平台上,文件锁是强制性的,这意味着文件锁会阻止任何应用程序访问该文件。

try {
    // Get a file channel for the file
    File file = new File("filename");
    FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

    // Use the file channel to create a lock on the file.
    // This method blocks until it can retrieve the lock.
    FileLock lock = channel.lock();

    // Try acquiring the lock without blocking. This method returns
    // null or throws an exception if the file is already locked.
    try {
        lock = channel.tryLock();
    } catch (OverlappingFileLockException e) {
        // File is already locked in this thread or virtual machine
    }

    // Release the lock
    lock.release();

    // Close the file
    channel.close();
} catch (Exception e) {
}

Linux, by default, uses voluntary locking, while Windows enforces it. Maybe you could detect the OS, and use renameTo() under UNIX with some locking code for Windows?

默认情况下,Linux 使用自愿锁定,而 Windows 强制执行它。也许您可以检测操作系统,并在 UNIX 下使用 renameTo() 并为 Windows 提供一些锁定代码?

There's also a way to turn on mandatory locking under Linux for specific files, but it's kind of obscure. You have to set the mode bits just right.

还有一种方法可以在 Linux 下为特定文件打开强制锁定,但它有点晦涩。您必须正确设置模式位。

Linux, following System V (see System V Interface Definition (SVID) Version 3), lets the sgid bit for files without group execute permission mark the file for mandatory locking

Linux 遵循 System V(请参阅 System V 接口定义 (SVID) 版本 3),让没有组执行权限的文件的 sgid 位标记为强制锁定的文件

回答by TofuBeer

回答by bruno conde

If this should be cross-platform I suggest 2 options:

如果这应该是跨平台的,我建议两个选项:

  1. Implement an intermediate service that is responsible for all the file accesses. Here you can use several mechanisms for synchronizing the requests. Each client java app accesses the file only through this service.
  2. Create a controlfile each time you need to perform synchronized operations. Each java app that accesses the file is responsible checking for the controlfile and waiting while this controlfile exists. (almost like a semaphore). The process doing the delete/rename operation is responsible for creating/deleting the controlfile.
  1. 实施负责所有文件访问的中间服务。在这里,您可以使用多种机制来同步请求。每个客户端 java 应用程序仅通过此服务访问文件。
  2. 每次需要执行同步操作时创建一个控制文件。每个访问该文件的 java 应用程序负责检查控制文件并等待该控制文件存在。(几乎就像一个信号量)。执行删除/重命名操作的进程负责创建/删除控制文件。

回答by MicSim

As stated here, it looks like the Windows OS doesn't even support atomic file rename for older versions. It's very likely you have to use some manual locking mechanisms or some kind of transactions. For that, you might want to take a look into the apache commons transactionpackage.

至于说在这里,它看起来像Windows操作系统竟然不支持原子文件重命名为旧版本。您很可能必须使用一些手动锁定机制或某种事务。为此,您可能需要查看apache commons 事务包。

回答by Lawrence Dol

If the purpose of the rename is to replace resource.txt on the fly andyou have control over all the programs involved, andthe frequency of replacement is not high, you could do the following.

如果重命名的目的是动态替换resource.txt,并且您可以控制所有涉及的程序,并且替换频率不高,则可以执行以下操作。

To open/read the file:

打开/读取文件:

  1. Open "resource.txt", if that fails
  2. Open "resource.old.txt", if that fails
  3. Open "resource.txt" again, if that fails
  4. You have an error condition.
  1. 打开“resource.txt”,如果失败
  2. 打开“resource.old.txt”,如果失败
  3. 再次打开“resource.txt”,如果失败
  4. 你有一个错误条件。

To replace the file:

要替换文件:

  1. Rename "resource.txt" to "resource.old.txt", then
  2. Rename "resource.new.txt" to "resource.txt", then
  3. Delete "resource.old.txt".
  1. 将“resource.txt”重命名为“resource.old.txt”,然后
  2. 将“resource.new.txt”重命名为“resource.txt”,然后
  3. 删除“resource.old.txt”。

Which will ensure all your readers always find a valid file.

这将确保您的所有读者始终找到有效的文件。

But, easier, would be to simply try your opening in a loop, like:

但是,更简单的方法是简单地尝试循环打开,例如:

InputStream inp=null;
StopWatch   tmr=new StopWatch();                     // made up class, not std Java
IOException err=null;

while(inp==null && tmr.elapsed()<5000) {             // or some approp. length of time
    try { inp=new FileInputStream("resource.txt"); }
    catch(IOException thr) { err=thr; sleep(100); }  // or some approp. length of time
    }

if(inp==null) {
     // handle error here - file did not turn up after required elapsed time
     throw new IOException("Could not obtain data from resource.txt file");
     }

... carry on

回答by rogerdpack

You might get some traction by establishing a filechannel lock on the file before renaming it (and deleting the file you're going to overwrite once you have the lock). -r

您可能会通过在重命名文件之前在文件上建立文件通道锁来获得一些吸引力(并删除获得锁后将覆盖的文件)。-r

回答by Eirik W

For Java 1.7+, use java.nio.file.Files.move(Path source, Path target, CopyOption... options)with CopyOptions "REPLACE_EXISTING" and "ATOMIC_MOVE".

对于 Java 1.7+,请java.nio.file.Files.move(Path source, Path target, CopyOption... options)与 CopyOptions“REPLACE_EXISTING”和“ATOMIC_MOVE”一起使用。

See API documentation for more information.

有关更多信息,请参阅 API 文档。

For example:

例如:

Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);

回答by Süniúr

I solve with a simple rename function.

我用一个简单的重命名函数来解决。

Calling :

呼叫:

File newPath = new File("...");
newPath = checkName(newPath);
Files.copy(file.toPath(), newPath.toPath(), StandardCopyOption.REPLACE_EXISTING);

The checkName function checks if exits. If exits then concat a number between two bracket (1) to the end of the filename. Functions:

checkName 函数检查是否退出。如果退出,则将两个括号 (1) 之间的数字连接到文件名的末尾。职能:

private static File checkName(File newPath) {
    if (Files.exists(newPath.toPath())) {

        String extractRegExSubStr = extractRegExSubStr(newPath.getName(), "\([0-9]+\)");
        if (extractRegExSubStr != null) {
            extractRegExSubStr = extractRegExSubStr.replaceAll("\(|\)", "");
            int parseInt = Integer.parseInt(extractRegExSubStr);
            int parseIntPLus = parseInt + 1;

            newPath = new File(newPath.getAbsolutePath().replace("(" + parseInt + ")", "(" + parseIntPLus + ")"));
            return checkName(newPath);
        } else {
            newPath = new File(newPath.getAbsolutePath().replace(".pdf", " (" + 1 + ").pdf"));
            return checkName(newPath);
        }

    }
    return newPath;

}

private static String extractRegExSubStr(String row, String patternStr) {
    Pattern pattern = Pattern.compile(patternStr);
    Matcher matcher = pattern.matcher(row);
    if (matcher.find()) {
        return matcher.group(0);
    }
    return null;
}

EDIT:Its only works for pdf. If you want other please replace the .pdf or create an extension paramter for it. NOTE: If the file contains additional numbers between brackets '(' then it may mess up your file names.

编辑:它仅适用于 pdf。如果您想要其他,请替换 .pdf 或为其创建扩展参数。注意:如果文件在方括号 '(' 之间包含其他数字,则可能会弄乱您的文件名。