web-dev-qa-db-fra.com

Comment renommer atomiquement un fichier en Java, même si le fichier dest existe déjà?

J'ai un groupe de machines, chacune exécutant une application Java app.

Ces Java applications doivent accéder à un resource.txt fichier de manière simultanée.

J'ai besoin de renommer atomiquement un temp.txt fichier vers resource.txt en Java, même si resource.txt existe déjà.

Suppression de resource.txt et renommer temp.txt ne fonctionne pas, car ce n'est pas atomique (cela crée un petit laps de temps où resource.txt n'existe pas).

Et ça devrait être multiplateforme ...

Merci !

38
Sébastien RoccaSerra

Pour Java 1.7+, utilisez Java.nio.file.Files.move(Path source, Path target, CopyOption... options) avec CopyOptions "REPLACE_EXISTING" et "ATOMIC_MOVE".

Voir la documentation de l'API pour plus d'informations.

Par exemple:

Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
36
Eirik W

Sous Linux (et je crois que Solaris et d'autres systèmes d'exploitation UNIX), la méthode File.renameTo () de Java écrasera le fichier de destination s'il existe, mais ce n'est pas le cas sous Windows.

Pour être multiplateforme, je pense que vous devez utiliser le verrouillage de fichier sur resource.txt, puis écraser les données.

Le comportement du verrou de fichier dépend de la plate-forme. Sur certaines plates-formes, le verrouillage de fichier est consultatif, ce qui signifie qu'à moins qu'une application vérifie un verrou de fichier, elle ne sera pas empêchée d'accéder au fichier. Sur d'autres plates-formes, le verrouillage de fichier est obligatoire, ce qui signifie qu'un verrou de fichier empêche toute application d'accéder au fichier.

try {
    // Get a file channel for the file
    File file = new File("filename");
    FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

    // Use the file channel to create a lock on the file.
    // This method blocks until it can retrieve the lock.
    FileLock lock = channel.lock();

    // Try acquiring the lock without blocking. This method returns
    // null or throws an exception if the file is already locked.
    try {
        lock = channel.tryLock();
    } catch (OverlappingFileLockException e) {
        // File is already locked in this thread or virtual machine
    }

    // Release the lock
    lock.release();

    // Close the file
    channel.close();
} catch (Exception e) {
}

Linux, par défaut, utilise le verrouillage volontaire, tandis que Windows l'applique. Peut-être pourriez-vous détecter le système d'exploitation et utiliser renameTo () sous UNIX avec un code de verrouillage pour Windows?

Il existe également un moyen d'activer le verrouillage obligatoire sous Linux pour des fichiers spécifiques, mais c'est un peu obscur. Vous devez définir les bits de mode correctement.

Linux, suivant System V (voir System V Interface Definition (SVID) Version 3), laisse le bit sgid pour les fichiers sans autorisation d'exécution de groupe marquer le fichier pour un verrouillage obligatoire

13
Stephen
7
TofuBeer

Comme indiqué ici , il semble que le système d'exploitation Windows ne prend même pas en charge le changement de nom de fichier atomique pour les anciennes versions. Il est très probable que vous deviez utiliser des mécanismes de verrouillage manuels ou des types de transactions. Pour cela, vous voudrez peut-être jeter un œil au package Apache commons transaction .

3
MicSim

Si cela doit être multiplateforme, je suggère 2 options:

  1. Implémentez un service intermédiaire responsable de tous les accès aux fichiers. Ici, vous pouvez utiliser plusieurs mécanismes pour synchroniser les requêtes. Chaque client Java app accède au fichier uniquement via ce service.
  2. Créez un fichier contrôle chaque fois que vous devez effectuer des opérations synchronisées. Chaque application Java qui accède au fichier est responsable de la vérification du fichier control et de l'attente pendant que ce fichier control existe. (Presque comme un sémaphore Le processus effectuant l'opération de suppression/changement de nom est responsable de la création/suppression du fichier contrôle.
1
bruno conde

Si le but du changement de nom est de remplacer resource.txt à la volée et vous avez le contrôle sur tous les programmes impliqués, et la fréquence de remplacement n'est pas élevée, vous pouvez faites ce qui suit.

Pour ouvrir/lire le fichier:

  1. Ouvrez "resource.txt", si cela échoue
  2. Ouvrez "resource.old.txt", si cela échoue
  3. Ouvrez à nouveau "resource.txt", si cela échoue
  4. Vous avez une condition d'erreur.

Pour remplacer le fichier:

  1. Renommez "resource.txt" en "resource.old.txt", puis
  2. Renommez "resource.new.txt" en "resource.txt", puis
  3. Supprimez "resource.old.txt".

Ce qui garantira que tous vos lecteurs trouveront toujours un fichier valide.

Mais, plus facile, ce serait simplement d'essayer votre ouverture en boucle, comme:

InputStream inp=null;
StopWatch   tmr=new StopWatch();                     // made up class, not std Java
IOException err=null;

while(inp==null && tmr.elapsed()<5000) {             // or some approp. length of time
    try { inp=new FileInputStream("resource.txt"); }
    catch(IOException thr) { err=thr; sleep(100); }  // or some approp. length of time
    }

if(inp==null) {
     // handle error here - file did not turn up after required elapsed time
     throw new IOException("Could not obtain data from resource.txt file");
     }

... carry on
1
Lawrence Dol

Vous pouvez obtenir une certaine traction en établissant un verrou de canal de fichier sur le fichier avant de le renommer (et en supprimant le fichier que vous allez écraser une fois que vous avez le verrou). -r

1
rogerdpack

Je résous avec une simple fonction de changement de nom.

Appel:

File newPath = new File("...");
newPath = checkName(newPath);
Files.copy(file.toPath(), newPath.toPath(), StandardCopyOption.REPLACE_EXISTING);

La fonction checkName vérifie si se termine. Si exit, concatez un nombre entre deux crochets (1) à la fin du nom de fichier. Les fonctions:

private static File checkName(File newPath) {
    if (Files.exists(newPath.toPath())) {

        String extractRegExSubStr = extractRegExSubStr(newPath.getName(), "\\([0-9]+\\)");
        if (extractRegExSubStr != null) {
            extractRegExSubStr = extractRegExSubStr.replaceAll("\\(|\\)", "");
            int parseInt = Integer.parseInt(extractRegExSubStr);
            int parseIntPLus = parseInt + 1;

            newPath = new File(newPath.getAbsolutePath().replace("(" + parseInt + ")", "(" + parseIntPLus + ")"));
            return checkName(newPath);
        } else {
            newPath = new File(newPath.getAbsolutePath().replace(".pdf", " (" + 1 + ").pdf"));
            return checkName(newPath);
        }

    }
    return newPath;

}

private static String extractRegExSubStr(String row, String patternStr) {
    Pattern pattern = Pattern.compile(patternStr);
    Matcher matcher = pattern.matcher(row);
    if (matcher.find()) {
        return matcher.group(0);
    }
    return null;
}

EDIT: Il ne fonctionne que pour pdf. Si vous en voulez d'autres, remplacez le .pdf ou créez un paramètre d'extension pour celui-ci. REMARQUE: Si le fichier contient des nombres supplémentaires entre crochets '(', cela peut perturber vos noms de fichiers.

0
SüniÚr