Zuverlässige File.renameTo () Alternative unter Windows?

92

Java File.renameTo()ist problematisch, besonders unter Windows, wie es scheint. Wie in der API-Dokumentation angegeben ,

Viele Aspekte des Verhaltens dieser Methode sind von Natur aus plattformabhängig: Der Umbenennungsvorgang kann eine Datei möglicherweise nicht von einem Dateisystem in ein anderes verschieben, ist möglicherweise nicht atomar und erfolgreich, wenn eine Datei mit dem abstrakten Zielpfadnamen erstellt wird ist bereits vorhanden. Der Rückgabewert sollte immer überprüft werden, um sicherzustellen, dass der Umbenennungsvorgang erfolgreich war.

In meinem Fall muss ich im Rahmen eines Upgrade-Vorgangs ein Verzeichnis verschieben (umbenennen), das möglicherweise Gigabyte an Daten enthält (viele Unterverzeichnisse und Dateien unterschiedlicher Größe). Das Verschieben erfolgt immer innerhalb derselben Partition / Laufwerk, sodass nicht wirklich alle Dateien auf der Festplatte physisch verschoben werden müssen.

Es sollten keine Dateisperren für den Inhalt des zu verschiebenden Verzeichnisses vorhanden sein, aber dennoch erledigt renameTo () häufig seine Aufgabe nicht und gibt false zurück. (Ich vermute nur, dass einige Dateisperren unter Windows möglicherweise etwas willkürlich ablaufen.)

Derzeit habe ich eine Fallback-Methode, die das Kopieren und Löschen verwendet, aber das ist zum Kotzen, da es je nach Größe des Ordners viel Zeit in Anspruch nehmen kann. Ich denke auch darüber nach, einfach die Tatsache zu dokumentieren, dass der Benutzer den Ordner manuell verschieben kann, um möglicherweise stundenlanges Warten zu vermeiden. Aber der richtige Weg wäre offensichtlich etwas Automatisches und Schnelles.

Meine Frage ist also, ob Sie einen alternativen, zuverlässigen Ansatz kennen, um mit Java unter Windows schnell zu verschieben / umzubenennen , entweder mit einfachem JDK oder einer externen Bibliothek. Oder wenn Sie eine einfache Möglichkeit kennen, Dateisperren für einen bestimmten Ordner und den gesamten Inhalt (möglicherweise Tausende einzelner Dateien) zu erkennen und aufzuheben , ist dies ebenfalls in Ordnung.


Bearbeiten : In diesem speziellen Fall scheinen wir davongekommen zu sein, indem wir nur renameTo()ein paar weitere Dinge berücksichtigt haben. siehe diese Antwort .

Jonik
quelle
3
Sie könnten warten / JDK 7 verwenden, das eine viel bessere Dateisystemunterstützung bietet.
Akarnokd
@ kd304, eigentlich kann ich nicht warten oder eine Early-Access-Version verwenden, aber interessant zu wissen, dass so etwas auf dem Weg ist!
Jonik

Antworten:

52

Siehe auch die Files.move()Methode in JDK 7.

Ein Beispiel:

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);
}
Alan
quelle
7
Leider ist Java7 nicht immer die Antwort (wie 42 ist)
wuppi
1
Selbst unter Ubuntu, JDK7, hatten wir dieses Problem, als wir Code auf EC2 mit EBS-Speicher ausführten. File.renameTo ist fehlgeschlagen, File.canWrite auch.
Saurabheights
Beachten Sie, dass dies genauso unzuverlässig ist wie File # renameTo (). Es gibt nur einen nützlicheren Fehler, wenn es fehlschlägt. Die einzige einigermaßen zuverlässige Möglichkeit, die ich gefunden habe, besteht darin, die Datei mit "Dateien kopieren" auf den neuen Namen zu kopieren und dann das Original mit "Dateien löschen" zu löschen (das Löschen selbst kann ebenfalls fehlschlagen, aus demselben Grund, aus dem "Dateien verschieben" möglicherweise fehlschlägt). .
Jwenting
26

Für das, was es wert ist, einige weitere Begriffe:

  1. Unter Windows renameTo()scheint dies fehlzuschlagen, wenn das Zielverzeichnis vorhanden ist, auch wenn es leer ist. Dies überraschte mich, als ich es unter Linux versucht hatte, wo es renameTo()erfolgreich war, wenn das Ziel existierte, solange es leer war.

    (Natürlich hätte ich nicht annehmen sollen, dass so etwas plattformübergreifend gleich funktioniert. Genau davor warnt der Javadoc.)

  2. Wenn Sie den Verdacht haben, dass einige Dateisperren bestehen bleiben, kann es hilfreich sein , ein wenig zu warten, bevor das Verschieben / Umbenennen erfolgt . (An einer Stelle in unserem Installationsprogramm / Upgrader haben wir für etwa 10 Sekunden eine "Schlaf" -Aktion und einen unbestimmten Fortschrittsbalken hinzugefügt, da möglicherweise ein Dienst an einigen Dateien hängt.) Führen Sie möglicherweise sogar einen einfachen Wiederholungsmechanismus durch, der dies versucht renameTo()und dann eine Zeitspanne wartet (die möglicherweise allmählich zunimmt), bis der Vorgang erfolgreich ist oder eine Zeitüberschreitung erreicht ist.

In meinem Fall scheinen die meisten Probleme durch Berücksichtigung der beiden oben genannten Punkte gelöst worden zu sein, sodass wir schließlich keinen nativen Kernel-Aufruf oder ähnliches durchführen müssen.

Jonik
quelle
2
Ich akzeptiere vorerst meine eigene Antwort, da sie beschreibt, was in unserem Fall geholfen hat. Wenn jemand eine gute Antwort auf das allgemeinere Problem mit renameTo () findet, können Sie diese gerne posten, und ich werde die akzeptierte Antwort gerne erneut prüfen.
Jonik
4
6,5 Jahre später denke ich, dass es Zeit ist, stattdessen die JDK 7-Antwort zu akzeptieren , zumal so viele Leute sie für hilfreich halten. =)
Jonik
19

In dem ursprünglichen Beitrag wurde "ein alternativer, zuverlässiger Ansatz zum schnellen Verschieben / Umbenennen mit Java unter Windows, entweder mit einfachem JDK oder einer externen Bibliothek", angefordert.

Eine weitere Option, die hier noch nicht erwähnt wurde, ist Version 1.3.2 oder höher der Bibliothek apache.commons.io , die FileUtils.moveFile () enthält .

Es wird eine IOException ausgelöst, anstatt bei einem Fehler boolean false zurückzugeben.

Siehe auch die Antwort von big lep in diesem anderen Thread .

MykennaC
quelle
2
Es sieht auch so aus, als würde JDK 1.7 eine bessere Unterstützung für Dateisystem-E / A bieten. Überprüfen Sie java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 hat keine Methodejava.nio.file.Path.moveTo()
Malte Schwerhoff
5

In meinem Fall schien es sich um ein totes Objekt in meiner eigenen Anwendung zu handeln, das diese Datei im Griff hatte. Diese Lösung hat also bei mir funktioniert:

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

Vorteil: Es ist ziemlich schnell, da es kein Thread.sleep () mit einer bestimmten fest codierten Zeit gibt.

Nachteil: Diese Grenze von 20 ist eine fest codierte Zahl. In all meinen Tests ist i = 1 genug. Aber um sicher zu sein, habe ich es mit 20 verlassen.

Wuppi
quelle
1
Ich habe etwas Ähnliches gemacht, aber mit 100ms Schlaf in der Schleife.
Lawrence Dol
4

Ich weiß, dass dies ein wenig hackig erscheint, aber für das, wofür ich es gebraucht habe, scheinen gepufferte Leser und Autoren kein Problem damit zu haben, die Dateien zu erstellen.

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

}

Funktioniert gut für kleine Textdateien als Teil eines Parsers. Stellen Sie nur sicher, dass oldName und newName vollständige Pfade zu den Dateispeicherorten sind.

Prost Kactus

Kactus
quelle
4

Der folgende Code ist KEINE 'Alternative', hat aber in Windows- und Linux-Umgebungen zuverlässig funktioniert:

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();
          }
    }
}
verrücktes Pferd
quelle
2
Hmm, dieser Code löscht srcFile, auch wenn renameTo (oder destFile.delete) fehlschlägt und die Methode eine IOException auslöst. Ich bin mir nicht sicher, ob das eine gute Idee ist.
Jonik
1
@ Jonik, Thanx, fester Code, um die src-Datei nicht zu löschen, wenn das Umbenennen fehlschlägt.
verrücktes Pferd
Vielen Dank für das Teilen dieses Problems, das mein Umbenennungsproblem unter Windows behoben hat.
BillMan
3

Unter Windows benutze ich Runtime.getRuntime().exec("cmd \\c ") und dann die Befehlszeilen-Umbenennungsfunktion, um Dateien tatsächlich umzubenennen. Es ist viel flexibler, z. B. wenn Sie die Erweiterung aller txt-Dateien in einem Verzeichnis umbenennen möchten, schreiben Sie dies einfach in den Ausgabestream:

benenne * .txt * .bak um

Ich weiß, dass es keine gute Lösung ist, aber anscheinend hat es bei mir immer funktioniert, viel besser als der Java-Inline-Support.

Johnydep
quelle
Super, das ist viel besser! Vielen Dank! :-)
Gaffcz
2

Warum nicht....

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

funktioniert auf nwindows 7, macht nichts, wenn existiveFile nicht existiert, könnte aber offensichtlich besser instrumentiert werden, um dies zu beheben.

Gepäck
quelle
2

Ich hatte ein ähnliches Problem. Die Datei wurde unter Windows kopiert, funktionierte aber unter Linux gut. Ich habe das Problem behoben, indem ich den geöffneten fileInputStream geschlossen habe, bevor ich renameTo () aufgerufen habe. Getestet unter Windows XP.

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

In meinem Fall befand sich der Fehler im Pfad des übergeordneten Verzeichnisses. Vielleicht ein Fehler, ich musste den Teilstring verwenden, um einen korrekten Pfad zu erhalten.

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

Ich weiß, dass es scheiße ist, aber eine Alternative besteht darin, ein Bat-Skript zu erstellen, das etwas Einfaches wie "SUCCESS" oder "ERROR" ausgibt, es aufzurufen, auf die Ausführung zu warten und dann seine Ergebnisse zu überprüfen.

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

Dieser Thread kann interessant sein. Überprüfen Sie auch die Process-Klasse, wie die Konsolenausgabe eines anderen Prozesses gelesen wird.

Ravi Wallau
quelle
-2

Sie können Robocopy versuchen . Dies ist nicht gerade "Umbenennen", aber es ist sehr zuverlässig.

Robocopy wurde für die zuverlässige Spiegelung von Verzeichnissen oder Verzeichnisbäumen entwickelt. Es verfügt über Funktionen, mit denen sichergestellt wird, dass alle NTFS-Attribute und -Eigenschaften kopiert werden, und enthält zusätzlichen Neustartcode für unterbrochene Netzwerkverbindungen.

Anton Gogolev
quelle
Vielen Dank. Aber da Robocopy keine Java-Bibliothek ist, wäre es wahrscheinlich nicht sehr einfach, sie aus meinem Java-Code zu bündeln und zu verwenden ...
Jonik
-2

Um eine Datei zu verschieben / umzubenennen, können Sie folgende Funktion verwenden:

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

Es ist in kernel32.dll definiert.

Blind
quelle
1
Ich bin der Meinung, dass die Mühe, dies in JNI zu verpacken, größer ist als der Aufwand, der erforderlich ist, um die Robokopie in einen Prozessdekorateur einzupacken.
Kevin Montrose
Ja, das ist der Preis, den Sie für die Abstraktion zahlen - und wenn sie leckt, leckt sie gut = D
Chii
Danke, ich könnte das in Betracht ziehen, wenn es nicht zu kompliziert wird. Ich habe JNI noch nie verwendet und konnte keine guten Beispiele für das Aufrufen einer Windows-Kernelfunktion unter SO finden. Daher habe
Jonik
Sie können einen generischen JNI-Wrapper wie johannburkard.de/software/nativecall ausprobieren, da dies ein ziemlich einfacher Funktionsaufruf ist.
Peter Smith
-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

Das Obige ist der einfache Code. Ich habe unter Windows 7 getestet und funktioniert einwandfrei.

iltaf khalid
quelle
11
Es gibt Fälle, in denen renameTo () nicht zuverlässig funktioniert. Das ist der springende Punkt der Frage.
Jonik