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 !
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);
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
Voici une discussion qui concerne: http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=401759
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 .
Si cela doit être multiplateforme, je suggère 2 options:
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:
Pour remplacer le fichier:
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
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
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.