Alternative fiable à File.renameTo () sur Windows?

92

Java File.renameTo()est problématique, en particulier sous Windows, semble-t-il. Comme le dit la documentation de l' API ,

De nombreux aspects du comportement de cette méthode sont intrinsèquement dépendants de la plate-forme: l'opération de changement de nom peut ne pas être en mesure de déplacer un fichier d'un système de fichiers à un autre, elle peut ne pas être atomique et elle peut échouer si un fichier avec le chemin abstrait de destination existe déjà. La valeur de retour doit toujours être vérifiée pour s'assurer que l'opération de changement de nom a réussi.

Dans mon cas, dans le cadre d'une procédure de mise à niveau, j'ai besoin de déplacer (renommer) un répertoire qui peut contenir des gigaoctets de données (beaucoup de sous-répertoires et de fichiers de différentes tailles). Le déplacement est toujours effectué dans la même partition / lecteur, il n'est donc pas vraiment nécessaire de déplacer physiquement tous les fichiers sur le disque.

Il ne devrait y avoir aucun verrou de fichier sur le contenu du répertoire à déplacer, mais encore, assez souvent, renameTo () échoue à faire son travail et retourne false. (Je suppose juste que certains verrous de fichiers expirent peut-être de manière quelque peu arbitraire sous Windows.)

Actuellement, j'ai une méthode de secours qui utilise la copie et la suppression, mais cela craint car cela peut prendre beaucoup de temps, en fonction de la taille du dossier. J'envisage également de simplement documenter le fait que l'utilisateur peut déplacer le dossier manuellement pour éviter d'attendre des heures, potentiellement. Mais la bonne voie serait évidemment quelque chose d'automatique et rapide.

Ma question est donc la suivante: connaissez-vous une approche alternative et fiable pour effectuer un déplacement / renommer rapide avec Java sur Windows , soit avec le JDK ordinaire ou une bibliothèque externe. Ou si vous connaissez un moyen simple de détecter et de libérer les verrous de fichiers pour un dossier donné et tout son contenu (éventuellement des milliers de fichiers individuels), ce serait bien aussi.


Edit : Dans ce cas particulier, il semble que nous nous en sommes sortis renameTo()en prenant simplement en compte quelques éléments supplémentaires; voir cette réponse .

Jonik
la source
3
Vous pouvez attendre / utiliser JDK 7, qui a un bien meilleur support du système de fichiers.
akarnokd le
@ kd304, en fait je ne peux pas attendre ou utiliser une version à accès anticipé, mais intéressant de savoir que quelque chose comme ça est en route!
Jonik le

Réponses:

52

Voir aussi la Files.move()méthode dans JDK 7.

Un exemple:

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
la source
7
malheureusement Java7 n'est pas toujours la réponse (comme 42 l'est)
wuppi
1
Même sur ubuntu, JDK7, nous avons rencontré ce problème lors de l'exécution de code sur EC2 avec stockage EBS. File.renameTo a échoué, tout comme File.canWrite.
saurabheights
Gardez à l'esprit que cela n'est pas aussi fiable que File # renameTo (). Cela donne juste une erreur plus utile en cas d'échec. Le seul moyen raisonnablement fiable que j'ai trouvé est de copier le fichier avec Files # copy vers le nouveau nom, puis de supprimer l'original à l'aide de Files # delete (qui se supprime peut également échouer, pour la même raison que le déplacement de Files # peut échouer) .
jwenting le
26

Pour ce que ça vaut, quelques notions supplémentaires:

  1. Sous Windows, renameTo()semble échouer si le répertoire cible existe, même s'il est vide. Cela m'a surpris, comme j'avais essayé sous Linux, où renameTo()réussi si la cible existait, tant qu'elle était vide.

    (De toute évidence, je n'aurais pas dû supposer que ce genre de chose fonctionne de la même manière sur toutes les plates-formes; c'est exactement ce que le Javadoc met en garde.)

  2. Si vous pensez qu'il peut y avoir des verrous de fichiers persistants, attendre un peu avant le déplacement / changement de nom peut aider. (À un moment donné de notre programme d'installation / mise à jour, nous avons ajouté une action "veille" et une barre de progression indéterminée pendant environ 10 secondes, car il se peut qu'un service se bloque sur certains fichiers). Peut-être même faire un simple mécanisme de nouvelle tentative qui essaie renameTo(), puis attend pendant une période (qui augmente peut-être progressivement), jusqu'à ce que l'opération réussisse ou qu'un délai d'attente soit atteint.

Dans mon cas, la plupart des problèmes semblent avoir été résolus en tenant compte des deux éléments ci-dessus, nous n'aurons donc pas besoin de faire un appel natif du noyau, ou quelque chose du genre, après tout.

Jonik
la source
2
J'accepte ma propre réponse pour le moment, car elle décrit ce qui a aidé dans notre cas. Néanmoins, si quelqu'un propose une excellente réponse au problème plus général de renameTo (), n'hésitez pas à publier et je serai heureux de reconsidérer la réponse acceptée.
Jonik
4
6,5 ans plus tard, je pense qu'il est temps d'accepter la réponse du JDK 7 à la place, d'autant plus que beaucoup de gens la considèrent utile. =)
Jonik
19

Le message original demandait "une approche alternative et fiable pour effectuer un déplacement / renommer rapide avec Java sur Windows, soit avec le JDK ordinaire ou une bibliothèque externe."

Une autre option non encore mentionnée ici est la version 1.3.2 ou ultérieure de la bibliothèque apache.commons.io , qui inclut FileUtils.moveFile () .

Il lève une IOException au lieu de renvoyer la valeur booléenne false en cas d'erreur.

Voir aussi la réponse de big lep dans cet autre fil .

MykennaC
la source
2
En outre, il semble que JDK 1.7 inclura une meilleure prise en charge des E / S du système de fichiers. Découvrez java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 n'a pas de méthodejava.nio.file.Path.moveTo()
Malte Schwerhoff
5

Dans mon cas, cela semblait être un objet mort au sein de ma propre application, qui gardait une poignée sur ce fichier. Donc, cette solution a fonctionné pour moi:

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

Avantage: c'est assez rapide, car il n'y a pas de Thread.sleep () avec un temps spécifique codé en dur.

Inconvénient: cette limite de 20 est un nombre codé en dur. Dans tous mes tests, i = 1 suffit. Mais pour être sûr, je l'ai laissé à 20 ans.

wuppi
la source
1
J'ai fait une chose similaire, mais avec un sommeil de 100 ms dans la boucle.
Lawrence Dol
4

Je sais que cela semble un peu piraté, mais pour ce pour quoi j'en ai eu besoin, il semble que les lecteurs et les écrivains tamponnés n'aient aucun problème à créer les fichiers.

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

}

Fonctionne bien pour les petits fichiers texte dans le cadre d'un analyseur, assurez-vous simplement que oldName et newName sont des chemins complets vers les emplacements des fichiers.

Vive Kactus

Kactus
la source
4

Le morceau de code suivant n'est PAS une `` alternative '' mais a fonctionné de manière fiable pour moi sur les environnements Windows et 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 tours
la source
2
Hmm, ce code supprime srcFile même si renameTo (ou destFile.delete) échoue et que la méthode lève IOException; Je ne sais pas si c'est une bonne idée.
Jonik
1
@Jonik, Thanx, code fixe pour ne pas supprimer le fichier src si le changement de nom échoue.
cheval fou
Merci d'avoir partagé cette résolution de mon problème de changement de nom sur Windows.
BillMan le
3

Sur Windows, j'utilise Runtime.getRuntime().exec("cmd \\c ")puis j'utilise la fonction de changement de nom en ligne de commande pour renommer les fichiers. C'est beaucoup plus flexible, par exemple si vous voulez renommer l'extension de tous les fichiers txt dans un répertoire en bak, écrivez simplement ceci dans le flux de sortie:

renommer * .txt * .bak

Je sais que ce n'est pas une bonne solution, mais apparemment, cela a toujours fonctionné pour moi, bien mieux que le support en ligne Java.

Johnydep
la source
Super, c'est bien mieux! Merci! :-)
gaffcz
2

Pourquoi pas....

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

fonctionne sur nwindows 7, ne fait rien si existingFile n'existe pas, mais il pourrait évidemment être mieux instrumenté pour résoudre ce problème.

bagages
la source
2

J'ai eu un problème similaire. Le fichier a été copié plutôt en mouvement sous Windows mais a bien fonctionné sous Linux. J'ai résolu le problème en fermant le fileInputStream ouvert avant d'appeler renameTo (). Testé sur Windows XP.

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

Dans mon cas, l'erreur était dans le chemin du répertoire parent. Peut-être un bug, j'ai dû utiliser la sous-chaîne pour obtenir un chemin correct.

        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
la source
0

Je sais que ça craint, mais une alternative est de créer un script chauve-souris qui produit quelque chose de simple comme "SUCCESS" ou "ERROR", l'invoquez, attendez qu'il soit exécuté et vérifiez ses résultats.

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

Ce fil peut être intéressant. Vérifiez également la classe Process pour savoir comment lire la sortie de console d'un processus différent.

Ravi Wallau
la source
-2

Vous pouvez essayer robocopy . Ce n'est pas exactement "renommer", mais c'est très fiable.

Robocopy est conçu pour une mise en miroir fiable des répertoires ou des arborescences de répertoires. Il dispose de fonctionnalités garantissant que tous les attributs et propriétés NTFS sont copiés, et inclut un code de redémarrage supplémentaire pour les connexions réseau sujettes à interruption.

Anton Gogolev
la source
Merci. Mais comme robocopy n'est pas une bibliothèque Java, il ne serait probablement pas très facile de (le regrouper et) de l'utiliser à partir de mon code Java ...
Jonik
-2

Pour déplacer / renommer un fichier, vous pouvez utiliser cette fonction:

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

Il est défini dans kernel32.dll.

Aveugle
la source
1
J'ai le sentiment que la difficulté d'envelopper cela dans JNI est plus grande que l'effort requis pour envelopper la robocopie dans un décorateur de processus.
Kevin Montrose
oui, c'est le prix que vous payez pour l'abstraction - et quand elle fuit, elle fuit bien = D
Chii
Merci, je pourrais envisager cela si cela ne devient pas trop compliqué. Je n'ai jamais utilisé JNI, et je n'ai pas pu trouver de bons exemples d'appels d'une fonction de noyau Windows sur SO, j'ai donc posté cette question: stackoverflow.com/questions/1000723
...
Vous pouvez essayer un wrapper JNI générique comme johannburkard.de/software/nativecall car il s'agit d'un appel de fonction assez simple.
Peter Smith
-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

Ce qui précède est le code simple. J'ai testé sur Windows 7 et fonctionne parfaitement bien.

iltaf khalid
la source
11
Il y a des cas où renameTo () ne fonctionne pas de manière fiable; c'est tout l'objet de la question.
Jonik