即使目标文件已经存在,如何在 Java 中自动重命名文件?

发布于 2024-07-14 10:58:37 字数 360 浏览 5 评论 0原文

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

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

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

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

而且应该是跨平台的...

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

These Java apps need to access a unique resource.txt file concurrently.

I need to atomically rename a temp.txt file to resource.txt in Java, even if resource.txt already exist.

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

And it should be cross-platform...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

撩心不撩汉 2024-07-21 10:58:38

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

有关详细信息,请参阅 API 文档。

例如:

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

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

See API documentation for more information.

For example:

Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
止于盛夏 2024-07-21 10:58:38

在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位
没有组执行权限标记
强制锁定的文件

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.

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

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?

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, 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

谜兔 2024-07-21 10:58:38

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

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 transaction package.

初见终念 2024-07-21 10:58:38

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

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

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 control file each time you need to perform synchronized operations. Each java app that accesses the file is responsible checking for the control file and waiting while this control file exists. (almost like a semaphore). The process doing the delete/rename operation is responsible for creating/deleting the control file.
讽刺将军 2024-07-21 10:58:38

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

要打开/读取文件:

  1. 打开“resource.txt”(如果失败)
  2. 打开“resource.old.txt”(如果失败)
  3. 再次打开“resource.txt”(如果失败)
  4. 您遇到错误情况。

要替换文件:

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

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

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

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

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

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.

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".

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
梦明 2024-07-21 10:58:38

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

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

青春有你 2024-07-21 10:58:38

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

调用:

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 或为其创建扩展参数。
注意:如果文件在方括号“(”之间包含其他数字,则可能会弄乱您的文件名。

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:

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文