Alternativa affidabile File.renameTo () su Windows?

Java File.renameTo() è problematico, specialmente su Windows, a quanto pare. Come dice la documentazione dell’API ,

Molti aspetti del comportamento di questo metodo sono intrinsecamente dipendenti dalla piattaforma: l’operazione di ridenominazione potrebbe non essere in grado di spostare un file da un file system a un altro, potrebbe non essere atomico e potrebbe non riuscire se un file con il percorso di destinazione è astratto esiste già. Il valore di ritorno dovrebbe sempre essere controllato per assicurarsi che l’operazione di rinomina abbia avuto successo.

Nel mio caso, come parte di una procedura di aggiornamento, ho bisogno di spostare (rinominare) una directory che può contenere gigabyte di dati (molte sottodirectory e file di varie dimensioni). La mossa avviene sempre all’interno della stessa partizione / unità, quindi non è necessario spostare fisicamente tutti i file sul disco.

Non ci dovrebbero essere blocchi di file per i contenuti della directory da spostare, ma ancora, abbastanza spesso, renameTo () non riesce a fare il suo lavoro e restituisce false. (Sto solo supponendo che forse alcuni blocchi di file scadano in modo un po ‘arbitrario su Windows.)

Attualmente ho un metodo di fallback che usa la copia e l’eliminazione, ma questo fa schifo perché potrebbe richiedere molto tempo, a seconda delle dimensioni della cartella. Sto anche considerando semplicemente di documentare il fatto che l’utente può spostare manualmente la cartella per evitare di aspettare per ore, potenzialmente. Ma il modo giusto sarebbe ovviamente qualcosa di automatico e veloce.

Quindi la mia domanda è : conosci un approccio alternativo e affidabile per fare una mossa / rinominare velocemente con Java su Windows , con JDK normale o qualche libreria esterna. O se si conosce un modo semplice per rilevare e rilasciare qualsiasi blocco di file per una determinata cartella e tutto il suo contenuto (possibilmente migliaia di singoli file), andrebbe bene anche questo.


Modifica : In questo caso particolare, sembra che siamo riusciti a scappare usando solo renameTo() prendendo in considerazione alcune altre cose; vedi questa risposta

Vedi anche il metodo Files.move() in JDK 7.

Un esempio:

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

Per quello che vale, alcune ulteriori nozioni:

  1. Su Windows, renameTo() sembra non riuscire se la directory di destinazione esiste, anche se è vuota. Questo mi ha sorpreso, come avevo provato su Linux, dove renameTo() avuto successo se il target esisteva, purché fosse vuoto.

    (Ovviamente non avrei dovuto presumere che questo tipo di cose funzionasse allo stesso modo su tutte le piattaforms: questo è esattamente ciò di cui Javadoc mette in guardia.)

  2. Se si sospetta che possano esserci dei blocchi persistenti, attendere un po ‘prima che lo spostamento / rinominazione possa essere d’ aiuto. (In un punto del nostro programma di installazione / aggiornamento abbiamo aggiunto un’azione di “sospensione” e una barra di avanzamento indeterminata per circa 10 secondi, perché potrebbe esserci un servizio su alcuni file). Forse anche fare un semplice meccanismo di riprova che prova a renameTo() , e poi aspetta un periodo (che magari aumenta gradualmente), fino a quando l’operazione ha successo o qualche timeout è raggiunto.

Nel mio caso, molti problemi sembrano essere stati risolti prendendo in considerazione entrambi i precedenti, quindi non avremo bisogno di fare una chiamata nativa al kernel, o qualcosa del genere, dopotutto.

Il post originale richiedeva “un approccio alternativo e affidabile per eseguire una mossa / rinominazione rapida con Java su Windows, sia con JDK semplice che con una libreria esterna”.

Un’altra opzione non menzionata ancora qui è v1.3.2 o successiva della libreria apache.commons.io , che include FileUtils.moveFile () .

Genera una IOException invece di restituire false booleane dopo errore.

Vedi anche la risposta di Big lep in questo altro thread .

Il seguente pezzo di codice NON è un ‘alternativa’, ma ha funzionato in modo affidabile per me su entrambi gli ambienti Windows e 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(); } } } 

Nel mio caso sembrava che fosse un object morto all’interno della mia applicazione, che manteneva un controllo su quel file. Quindi questa soluzione ha funzionato per me:

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

Vantaggio: è piuttosto veloce, in quanto non esiste Thread.sleep () con uno specifico tempo hardcoded.

Svantaggio: quel limite di 20 è un numero fisso. In tutti i miei test, i = 1 è sufficiente. Ma per essere sicuro l'ho lasciato alle 20.

So che questo sembra un po ‘hacky, ma per quello di cui ho avuto bisogno, sembra che i lettori e gli scrittori non abbiano problemi a creare i file.

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

Funziona bene per piccoli file di testo come parte di un parser, ma assicurati che oldName e newName siano percorsi completi per i percorsi dei file.

Cheers Kactus

Su Windows uso Runtime.getRuntime().exec("cmd \\c ") e poi uso la funzione di rinomina della riga di comando per rinominare effettivamente i file. È molto più flessibile, ad esempio se si desidera rinominare l’estensione di tutti i file txt in una directory da bak, basta scriverlo nello stream di output:

rinomina * .txt * .bak

So che non è una buona soluzione, ma a quanto pare ha sempre funzionato per me, molto meglio del supporto in linea Java.

Perchè no….

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

funziona su nwindows 7, non fa nulla se il file esistente non esiste, ma ovviamente potrebbe essere meglio strumentato per risolvere questo problema.

Nel mio caso, l’errore era nel percorso della directory superiore. Forse un bug, ho dovuto usare la sottostringa per ottenere un percorso corretto.

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

Ho avuto un problema simile. Il file è stato copiato piuttosto spostandosi su Windows ma ha funzionato bene su Linux. Ho risolto il problema chiudendo il file apertoInputStream prima di chiamare renameTo (). Testato su Windows XP.

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

So che fa schifo, ma un’alternativa è creare uno script bat che restituisca qualcosa di semplice come “SUCCESSO” o “ERRORE”, invocarlo, attendere che venga eseguito e quindi controllarne i risultati.

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

Questa discussione potrebbe essere interessante. Controllare anche la class Process su come leggere l’output della console di un processo diverso.

Puoi provare Robocopy . Questo non è esattamente “rinominare”, ma è molto affidabile.

Robocopy è progettato per il mirroring affidabile di directory o alberi di directory. Dispone di funzionalità per garantire che tutti gli attributi e le proprietà NTFS vengano copiati e include un codice di riavvio aggiuntivo per le connessioni di rete soggette a interruzioni.

Per spostare / rinominare un file è ansible utilizzare questa funzione:

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

È definito in kernel32.dll.

  File srcFile = new File(origFilename); File destFile = new File(newFilename); srcFile.renameTo(destFile); 

Quanto sopra è il codice semplice. Ho provato su Windows 7 e funziona perfettamente.