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


92

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

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

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

そこにはならない移動するディレクトリの内容に任意のファイルのロックも、それでも、かなり頻繁に、renameTo()は、その仕事とはfalseを返しますを行うには失敗しました。(Windowsでは、おそらくいくつかのファイルロックがいくらか勝手に期限切れになると思います。)

現在、私はコピーと削除を使用するフォールバックメソッドを持っていますが、フォルダーのサイズによっては時間がかかることがあるため、これは面倒です。また、ユーザーがフォルダーを手動で移動して、何時間も待たないようにすることができるという事実を単に文書化することも検討しています。しかし、正しい方法は明らかに自動的かつ迅速なものになります。

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


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


3
ファイルシステムのサポートがはるかに優れているJDK 7を待つ/使用することができます。
akarnokd 2009年

@ kd304、実際には私は待つことも、早期アクセスバージョンを使用することもできませんが、そのようなことを知っているのは興味深いことです!
ジョニック2009年

回答:


52

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

例:

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

7
残念ながら、Java7は必ずしも答えではありません(42がそうです)
wuppi

1
ubuntu、JDK7でも、EBSストレージを使用してEC2でコードを実行すると、この問題に直面しました。File.renameToは失敗し、File.canWriteも失敗しました。
saurabheights 2016

これはFile#renameTo()ほど信頼性が低いことに注意してください。失敗すると、より有用なエラーが表示されるだけです。私が見つけた合理的に信頼できる唯一の方法は、Files#copyを使用してファイルを新しい名前にコピーし、Files#deleteを使用して元のファイルを削除することです(同じ理由で、Files#moveも失敗する可能性があるため、削除も失敗する可能性があります)。 。
2019

26

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

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

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

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

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


2
私のケースで何が助けになったかを説明している今のところ、私は自分の答えを受け入れています。それでも、renameTo()のより一般的な問題に対する素晴らしい答えを誰かが思いついた場合は、遠慮なく投稿してください。受け入れられた答えを再考させていただきます。
Jonik、2009

4
6.5年後、JDK 7の回答を受け入れる時期が来たと思います。特に、多くの人が役立つと考えているためです。=)
Jonik

19

元の投稿では、「Windows上のJavaで、単純なJDKまたは外部ライブラリを使用してすばやく移動/名前を変更するための、信頼できる代替アプローチ」が要求されました。

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

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

この別のスレッドでの大きなlepの応答も参照してください。


2
また、JDK 1.7にはより優れたファイルシステムI / Oサポートが含まれるようです。java.nio.file.Path.moveTo()をチェックしてください:java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK 1.7には方法がありませんjava.nio.file.Path.moveTo()
Malte Schwerhoff、2015

5

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

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

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

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


1
私も同じようなことをしましたが、ループ内で100msのスリープがありました。
Lawrence Dol 2014

4

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

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がファイルの場所へのフルパスであることを確認してください。

乾杯カクタス


4

次のコードは「代替」ではありませんが、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();
          }
    }
}

2
うーん、このコードはrenameTo(またはdestFile.delete)が失敗してメソッドがIOExceptionをスローした場合でもsrcFileを削除します。それが良い考えかどうかはわかりません。
ジョニック

1
@ Jonik、Thanx、名前の変更に失敗した場合にsrcファイルを削除しないようにコードを修正しました。
クレイジーホース

この問題を解決してくれてありがとう。
BillMan、2011

3

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

* .txt * .bakの名前を変更する

私はそれが良い解決策ではないことを知っていますが、どうやらそれは常に私にとってうまくいきました。Javaインラインサポートよりもはるかに優れています。


スーパー、これははるかに優れています!ありがとう!:-)
gaffcz 2013年

2

何故なの....

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

nwindows 7で動作しますが、existingFileが存在しない場合は何もしませんが、明らかにこれを修正するために適切にインスツルメントできます。


2

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

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

1

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

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

0

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

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

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


-2

robocopyを試すことができます。これは厳密に「名前を変更する」ことではありませんが、非常に信頼できます。

Robocopyは、ディレクトリまたはディレクトリツリーの信頼できるミラーリング用に設計されています。すべてのNTFS属性とプロパティが確実にコピーされるようにする機能があり、ネットワーク接続が中断する可能性のある追加の再起動コードが含まれています。


ありがとう。しかし、robocopyはJavaライブラリではないため、おそらくJavaコードから(バンドルして)使用するのはそれほど簡単ではありません...
Jonik

-2

ファイルを移動/名前変更するには、次の関数を使用できます:

BOOL WINAPI MoveFile(
  __in  LPCTSTR lpExistingFileName,
  __in  LPCTSTR lpNewFileName
);

kernel32.dllで定義されています。


1
これをJNIでラップする問題は、プロセスデコレータでrobocopyをラップするのに必要な労力よりも大きいと感じています。
ケビンモントローズ

うん、これはあなたが抽象化に支払う価格です-そしてそれが漏れるとき、それは良い漏れます= D
Chii

おかげで、あまり複雑にならなければ、これを検討するかもしれません。私が今までにJNIを使用していない、と私はこの質問投稿ので、SO上でWindowsカーネル関数を呼び出すのに良い例を見つけることができませんでした:stackoverflow.com/questions/1000723/...
Jonik

これはかなり単純な関数呼び出しなのでjohannburkard.de / software / nativecallのような汎用JNIラッパーを試すことができます。
Peter Smith、

-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

上記は単純なコードです。私はWindows 7でテストし、完全に問題なく動作しました。


11
renameTo()が確実に機能しない場合があります。それが問題の核心です。
Jonik 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.