如何在Java中以原子方式重命名文件,即使dest文件已存在?

时间:2009-02-27 16:59:34

标签: java file cross-platform

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

这些Java应用程序需要能够快速访问唯一的resource.txt文件。

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

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

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

谢谢!

8 个答案:

答案 0 :(得分:32)

对于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.

例如:

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

答案 1 :(得分:13)

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

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

  

文件锁的行为是   平台依赖性。在某些平台上,   文件锁是咨询,这意味着   除非申请检查   文件锁定,不会被阻止   从访问该文件。在其他方面   平台,文件锁是必需的,   这意味着文件锁阻止   访问的任何应用程序   文件。

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使用自动锁定,而Windows会强制执行。也许你可以检测到操作系统,并在UNIX下使用renameTo()和Windows的某些锁定代码?

还有一种方法可以在Linux下为特定文件启用强制锁定,但这有点模糊。你必须正确设置模式位。

  

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

答案 2 :(得分:6)

答案 3 :(得分:2)

如上所述here,看起来Windows操作系统甚至不支持旧版本的原子文件重命名。您很可能必须使用某些手动锁定机制或某种交易。为此,您可能需要查看apache commons transaction包。

答案 4 :(得分:1)

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

  1. 实现负责所有文件访问的中间服务。在这里,您可以使用多种机制来同步请求。每个客户端Java应用程序仅通过此服务访问该文件。
  2. 每次需要执行同步操作时,创建控件文件。每个访问该文件的Java应用程序都负责检查控件文件,并在此控件文件存在时等待。 (几乎像一个信号量)。执行删除/重命名操作的过程负责创建/删除控件文件。

答案 5 :(得分:1)

如果重命名的目的是动态替换resource.txt ,您可以控制所涉及的所有程序,的替换频率不高,你可以做到以下几点。

打开/阅读文件:

  1. 打开“resource.txt”,如果失败
  2. 打开“resource.old.txt”,如果失败
  3. 再次打开“resource.txt”,如果失败
  4. 您的错误情况。
  5. 替换文件:

    1. 将“resource.txt”重命名为“resource.old.txt”,然后重命名
    2. 将“resource.new.txt”重命名为“resource.txt”,然后重命名为
    3. 删除“resource.old.txt”。
    4. 这将确保所有读者始终找到有效的文件。

      但是,更简单的是,只需在循环中尝试打开,例如:

      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
      

答案 6 :(得分:1)

通过在重命名文件之前在文件上建立文件通道锁定(以及删除锁定后要覆盖的文件),可能会获得一些牵引力。 -r

答案 7 :(得分:0)

我用一个简单的重命名功能解决了

通话:

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

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

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