web-dev-qa-db-fra.com

Reliable File.renameTo () alternative sur Windows?

La File.renameTo() de Java est problématique, surtout sous Windows, semble-t-il. Comme le dit documentation 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 d'accès abstrait de destination existe déjà. La valeur de retour doit toujours être vérifiée pour vous 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 des fichiers de tailles différentes). Le déplacement est toujours effectué sur la même partition/le même lecteur, il n'est donc pas vraiment nécessaire de déplacer physiquement tous les fichiers sur le disque.

Il n'y a ne devrait pas qu'il y ait des verrous de fichiers sur le contenu du répertoire à déplacer, mais encore, assez souvent, renameTo () ne fait pas son travail et retourne false. (Je suppose que certains verrous de fichiers expirent quelque peu arbitrairement 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, selon 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 façon serait évidemment quelque chose d'automatique et de rapide.

Donc ma question est, connaissez-vous une approche alternative fiable pour faire un déplacement rapide/renommer avec Java sur Windows , soit avec du JDK standard, soit avec une bibliothèque externe. Ou si vous connaissez un moyen facile de détecter et de libérer les verrous de fichiers pour un dossier donné et tout son contenu (peut-être des milliers de fichiers individuels), ce serait bien aussi.


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

87
Jonik

Voir aussi la méthode Files.move() 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);
}
45
Alan

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é sur Linux, où renameTo() réussissait si la cible existait, tant qu'elle était vide.

    (Évidemment, 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, attendez un peu avant le déplacement/renommage peut-être aide. (Dans un point de notre programme d'installation/mise à niveau, nous avons ajouté une action "veille" et une barre de progression indéterminée pendant environ 10 secondes, car il pourrait y avoir un service accroché à certains fichiers). Peut-être même faire un simple mécanisme de nouvelle tentative qui essaie renameTo(), puis attend un certain temps (qui peut augmenter progressivement), jusqu'à ce que l'opération réussisse ou qu'un certain délai soit atteint.

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

25
Jonik

Le message d'origine demandait "une approche alternative et fiable pour effectuer un déplacement rapide/renommer avec Java sous Windows, soit avec du JDK standard, soit avec 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 comprend FileUtils.moveFile () .

Il lève une IOException au lieu de renvoyer un booléen faux en cas d'erreur.

Voir également gros lepLa réponse de cet autre thread .

19
MykennaC

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
crazy horse

Dans mon cas, cela semblait être un objet mort dans ma propre application, qui gardait une poignée sur ce fichier. Cette solution a donc 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 une heure codée en dur spécifique.

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

4
wuppi

Je sais que cela semble un peu hacky, mais pour ce dont j'ai 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 d'accès complets aux emplacements des fichiers.

Cheers Kactus

3
Kactus

Sur Windows, j'utilise Runtime.getRuntime().exec("cmd \\c ") puis j'utilise la fonction de renommage en ligne de commande pour renommer les fichiers. Il est beaucoup plus flexible, par exemple, si vous voulez renommer l'extension de tous les fichiers txt dans un répertoire à 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 Java support en ligne.

2
Johnydep

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 Windows 7, ne fait rien si le fichier existant n'existe pas, mais pourrait évidemment être mieux instrumenté pour résoudre ce problème.

1
casgage

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

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

Dans mon cas, l'erreur se trouvait 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) {
           ...
1
Marcus Becker

Je sais que ça craint, mais une alternative est de créer un script bat qui génère quelque chose de simple comme "SUCCESS" ou "ERROR", l'invoquer, attendre qu'il soit exécuté puis vérifier ses résultats.

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

Ce fil peut être intéressant. Vérifiez également la classe Process sur la façon de lire la sortie de la console d'un processus différent.

0
Ravi Wallau