web-dev-qa-db-ja.com

destファイルがすでに存在する場合でも、Javaでファイルの名前をアトミックに変更する方法は?

マシンのクラスターがあり、それぞれがJavaアプリを実行しています。

これらのJavaアプリは、一意のresource.txtファイルに同時にアクセスする必要があります。

temp.txtがすでに存在していても、Javaでresource.txtファイルをresource.txtにアトミックに名前変更する必要があります。

resource.txtの削除とtemp.txtの名前変更は、アトミックではないため機能しません(resource.txtが存在しない小さなタイムフレームが作成されます)。

そしてそれはクロスプラットフォームでなければなりません...

よろしくお願いします!

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);
36
Eirik W

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では強制されます。多分あなたはOSを検出し、Windows用のいくつかのロックコードでUNIXの下でrenameTo()を使用できますか?

Linuxで特定のファイルに対して強制ロックをオンにする方法もありますが、それはあいまいです。モードビットを適切に設定する必要があります。

Linux、System V(System Vインターフェイス定義(SVID)バージョン3を参照)に従い、グループ実行権限のないファイルのsgidビットにより、必須ロックのファイルをマーク

13
Stephen

関連するディスカッションは次のとおりです。 http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=401759

7
TofuBeer

前述のように here は、Windows OSが古いバージョンのアトミックファイル名の変更さえサポートしていないようです。手動のロックメカニズムまたはある種のトランザクションを使用する必要がある可能性が高いです。そのためには、 Apache commonsトランザクション パッケージを調べてみてください。

3
MicSim

これがクロスプラットフォームである必要がある場合、2つのオプションを提案します。

  1. すべてのファイルアクセスを担当する中間サービスを実装します。ここでは、リクエストを同期するためのいくつかのメカニズムを使用できます。各クライアントJavaアプリは、このサービスを介してのみファイルにアクセスします。
  2. 同期操作を実行する必要があるたびにcontrolファイルを作成します。各Javaファイルにアクセスするアプリは、controlファイルをチェックし、このcontrolファイルが存在する間待機します)(ほぼセマフォのように)削除/名前変更操作を行うプロセスは、controlファイルの作成/削除を担当します。
1
bruno conde

名前変更の目的がオンザフライでresource.txtを置き換えることである場合and関係するすべてのプログラムを制御できますand置換の頻度が高くない場合は、以下をせよ。

ファイルを開く/読み取るには:

  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
1
Lawrence Dol

ファイル名を変更する前にファイルにfilechannelロックを設定する(そしてロックを取得したら上書きするファイルを削除する)と、ある程度のトラクションが得られる場合があります。 -r

1
rogerdpack

簡単なリネーム機能で解決します。

呼び出し:

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

CheckName関数は、終了するかどうかを確認します。存在する場合は、2つのブラケット(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を置き換えるか、.pdfの拡張パラメーターを作成してください。注:ファイルの角かっこ( ')の間に追加の番号が含まれている場合、ファイル名が混乱する可能性があります。

0
SüniÚr