web-dev-qa-db-ja.com

Windowsでの信頼できるFile.renameTo()の代替手段?

JavaのFile.renameTo()は、特にWindowsでは問題があるようです。 APIドキュメント が示すように、

このメソッドの動作の多くの側面は、本質的にプラットフォームに依存します。名前変更操作は、あるファイルシステムから別のファイルシステムにファイルを移動できない場合があり、アトミックではない場合がありますもう存在している。戻り値を常にチェックして、名前変更操作が成功したことを確認する必要があります。

私の場合、アップグレード手順の一環として、ギガバイトのデータ(多くのサブディレクトリとさまざまなサイズのファイル)を含むディレクトリを移動(名前変更)する必要があります。移動は常に同じパーティション/ドライブ内で行われるため、ディスク上のすべてのファイルを物理的に移動する必要はありません。

そこにshould n't移動するdirの内容に対するファイルロックがありますが、それでも、多くの場合、renameTo()はジョブを実行できず、falseを返します。 (Windowsでは、おそらくいくつかのファイルロックがある程度勝手に期限切れになると推測しています。)

現在、コピーと削除を使用するフォールバックメソッドがありますが、これは、フォルダーのサイズに応じてa lotの時間がかかるため、残念です。また、ユーザーがフォルダーを手動で移動して数時間待機する可能性があるという事実を単純に文書化することも検討しています。しかし、正しい方法は明らかに自動で迅速なものです。

だから私の質問は、WindowsでJavaを使って簡単な移動/名前変更を行う代替の信頼できるアプローチを知っていますか?外部ライブラリ。または、特定のフォルダのファイルロックを検出および解放するeasyの方法とその内容のすべて(おそらく数千の個別のファイル)を知っている場合、それも問題ありません。 。


Edit:この特定のケースでは、renameTo()だけを使用して、さらにいくつかのことを考慮して逃げたようです。 この回答 を参照してください。

87
Jonik

JDK 7の Files.move() メソッドも参照してください。

例:

String fileName = "MyFile.txt";

try {
    Files.move(new File(fileName).toPath(), new File(fileName).toPath(), Java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
    Logger.getLogger(SomeClass.class.getName()).log(Level.SEVERE, null, ex);
}
45
Alan

それが価値があるものについて、いくつかのさらなる概念:

  1. Windowsでは、ターゲットディレクトリが空であっても、ターゲットディレクトリが存在する場合、renameTo()は失敗するようです。 Linuxで試したところ、ターゲットが空であればrenameTo()が成功したので、これは私を驚かせました。

    (明らかに、この種のことはプラットフォーム間で同じように動作すると想定すべきではありません。これはまさにJavadocが警告していることです。)

  2. いくつかのファイルロックが残っていると思われる場合は、移動/名前変更の少し前に待機してくださいmight help。 (インストーラー/アップグレードプログラムのある時点で、一部のファイルにサービスがハングしている可能性があるため、約10秒間「スリープ」アクションと不確定な進行状況バーを追加しました)。おそらく、renameTo()を試行する単純な再試行メカニズムを実行し、その後、操作が成功するかタイムアウトに達するまで一定期間(徐々に増加する可能性があります)待機します。

私の場合、上記の両方を考慮に入れることでほとんどの問題は解決されたように見えるので、結局、ネイティブカーネルコールなどを行う必要はありません。

25
Jonik

元の投稿は、「WindowsでJava、プレーンJDKまたは外部ライブラリのいずれかを使用して、迅速な移動/名前変更を行うための代替の信頼できるアプローチです。」

ここでまだ言及されていない別のオプションは、v1.3.2以降の Apache.commons.io ライブラリで、これには FileUtils.moveFile() が含まれます。

エラー時にブール値falseを返す代わりにIOExceptionをスローします。

こちらもご覧ください ビッグレップthis other thread での応答。

19
MykennaC

次のコードは「代替」ではありませんが、Windows環境とLinux環境の両方で確実に機能します。

public static void renameFile(String oldName, String newName) throws IOException {
    File srcFile = new File(oldName);
    boolean bSucceeded = false;
    try {
        File destFile = new File(newName);
        if (destFile.exists()) {
            if (!destFile.delete()) {
                throw new IOException(oldName + " was not successfully renamed to " + newName); 
            }
        }
        if (!srcFile.renameTo(destFile))        {
            throw new IOException(oldName + " was not successfully renamed to " + newName);
        } else {
                bSucceeded = true;
        }
    } finally {
          if (bSucceeded) {
                srcFile.delete();
          }
    }
}
4
crazy horse

私の場合、それは自分のアプリケーション内の死んだオブジェクトのようで、そのファイルへのハンドルを保持していました。だからその解決策は私のために働いた:

for (int i = 0; i < 20; i++) {
    if (sourceFile.renameTo(backupFile))
        break;
    System.gc();
    Thread.yield();
}

利点:特定のハードコードされた時間を持つThread.sleep()がないため、非常に高速です。

欠点:20という制限は、ハードコードされた数値です。すべてのテストで、i = 1で十分です。しかし、確かに20のままにしておきました。

4
wuppi

これは少しハックのように思えますが、私がそれを必要としてきたことに関しては、バッファされたリーダーとライターはファイルを作成するのに問題がないようです。

void renameFiles(String oldName, String newName)
{
    String sCurrentLine = "";

    try
    {
        BufferedReader br = new BufferedReader(new FileReader(oldName));
        BufferedWriter bw = new BufferedWriter(new FileWriter(newName));

        while ((sCurrentLine = br.readLine()) != null)
        {
            bw.write(sCurrentLine);
            bw.newLine();
        }

        br.close();
        bw.close();

        File org = new File(oldName);
        org.delete();

    }
    catch (FileNotFoundException e)
    {
        e.printStackTrace();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

}

パーサーの一部として小さなテキストファイルに適しています。oldNameとnewNameがファイルの場所へのフルパスであることを確認してください。

乾杯カクタス

3
Kactus

Windowsでは、Runtime.getRuntime().exec("cmd \\c ")を使用し、コマンドラインの名前変更機能を使用して、実際にファイルの名前を変更します。たとえば、dir内のすべてのtxtファイルの拡張子をbakに変更して、出力ストリームに書き込むだけの場合は、はるかに柔軟です。

* .txt * .bakの名前を変更します

私はそれが良い解決策ではないことを知っていますが、明らかにそれはいつも私のために働いていた、はるかに良いJavaインラインサポート。

2
Johnydep

何故なの....

import com.Sun.jna.Native;
import com.Sun.jna.Library;

public class RenamerByJna {
    /* Requires jna.jar to be in your path */

    public interface Kernel32 extends Library {
        public boolean MoveFileA(String existingFileName, String newFileName);
    }

    public static void main(String[] args) {
        String path = "C:/yourchosenpath/";
        String existingFileName = path + "test.txt";
        String newFileName = path + "renamed.txt";

        Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
            kernel32.MoveFileA(existingFileName, newFileName);
        }
}

windows 7で動作し、既存のFileが存在しない場合は何もしませんが、明らかにこれを修正するためのより良いインストルメント化が可能です。

1
casgage

同様の問題がありました。ファイルはWindowsでは移動されてコピーされましたが、Linuxでは正常に機能しました。 renameTo()を呼び出す前に、開いているfileInputStreamを閉じることで問題を修正しました。 Windows XPでテスト済み。

fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);

私の場合、エラーは親ディレクトリのパスにありました。バグかもしれませんが、正しい文字列を取得するには部分文字列を使用する必要がありました。

        try {
            String n = f.getAbsolutePath();
            **n = n.substring(0, n.lastIndexOf("\\"));**
            File dest = new File(**n**, newName);
            f.renameTo(dest);
        } catch (Exception ex) {
           ...
1
Marcus Becker

私はそれが悪いことを知っていますが、別の方法は、「SUCCESS」や「ERROR」などの単純なものを出力するバットスクリプトを作成し、それを呼び出し、実行されるのを待ってから結果を確認することです。

Runtime.getRuntime()。exec( "cmd/c start test.bat");

このスレッドは興味深いかもしれません。別のプロセスのコンソール出力を読み取る方法については、Processクラスも確認してください。

0
Ravi Wallau