Affidabile alternativa File.renameTo () su Windows?


92

Java File.renameTo()è problematico, specialmente su Windows, a quanto pare. Come dice la documentazione 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 astratto di destinazione esiste già. Il valore restituito deve essere sempre controllato per assicurarsi che l'operazione di ridenominazione sia stata eseguita correttamente.

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). Lo spostamento viene sempre eseguito all'interno della stessa partizione / unità, quindi non è necessario spostare fisicamente tutti i file sul disco.

Non dovrebbe esserci alcun blocco di file sul contenuto della directory da spostare, ma ancora, abbastanza spesso, renameTo () non riesce a fare il suo lavoro e restituisce false. (Sto solo immaginando che forse alcuni blocchi di file scadono in modo arbitrario su Windows.)

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

Quindi la mia domanda è : conosci un approccio alternativo e affidabile per eseguire un rapido spostamento / rinomina con Java su Windows , con JDK semplice o con qualche libreria esterna. Oppure, se conosci un modo semplice per rilevare e rilasciare eventuali blocchi di file per una determinata cartella e tutti i suoi contenuti (possibilmente migliaia di singoli file), anche quello andrebbe bene.


Modifica : in questo caso particolare, sembra che siamo riusciti a usarlo solo renameTo()prendendo in considerazione alcune cose in più; vedi questa risposta .


3
Potresti aspettare / usare JDK 7, che ha un supporto del file system molto migliore.
akarnokd

@ kd304, in realtà non posso aspettare o utilizzare una versione ad accesso anticipato, ma è interessante sapere che qualcosa del genere è in arrivo!
Jonik

Risposte:


52

Vedi anche il Files.move()metodo 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);
}

7
purtroppo Java7 non è sempre la risposta (come lo è 42)
wuppi

1
Anche su Ubuntu, JDK7, abbiamo riscontrato questo problema durante l'esecuzione di codice su EC2 con archiviazione EBS. File.renameTo non è riuscito e anche File.canWrite.
saurabheights

Tieni presente che questo è inaffidabile come File # renameTo (). Fornisce solo un errore più utile quando fallisce. L'unico modo ragionevolmente affidabile che ho trovato è copiare il file con Files # copy con il nuovo nome, quindi eliminare l'originale usando Files # delete (che anche l'eliminazione potrebbe non riuscire, per lo stesso motivo per cui Files # move potrebbe non riuscire) .
jwenting

26

Per quello che vale, alcune nozioni aggiuntive:

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

    (Ovviamente non avrei dovuto presumere che questo genere di cose funzionasse allo stesso modo su tutte le piattaforme; questo è esattamente ciò di cui avverte Javadoc.)

  2. Se sospetti che ci possano essere alcuni blocchi di file persistenti, attendere un po 'prima che lo spostamento / rinomina possa aiutare. (In un punto del nostro programma di installazione / aggiornamento abbiamo aggiunto un'azione "sleep" e una barra di avanzamento indeterminata per circa 10 secondi, perché potrebbe esserci un servizio sospeso su alcuni file). Forse anche fare un semplice meccanismo di ripetizione che prova renameTo(), e poi attende un periodo (che magari aumenta gradualmente), fino a quando l'operazione riesce o viene raggiunto un timeout.

Nel mio caso, la maggior parte dei problemi sembra essere stata risolta tenendo conto di entrambi i precedenti, quindi non avremo bisogno di fare una chiamata nativa al kernel, o qualcosa del genere, dopo tutto.


2
Per ora accetto la mia risposta, poiché descrive ciò che ha aiutato nel nostro caso. Tuttavia, se qualcuno fornisce un'ottima risposta al problema più generale con renameTo (), sentiti libero di postare e sarò felice di riconsiderare la risposta accettata.
Jonik

4
6,5 anni dopo, penso che sia ora di accettare la risposta JDK 7 , soprattutto perché molte persone la considerano utile. =)
Jonik

19

Il post originale richiedeva "un approccio alternativo e affidabile per eseguire un rapido spostamento / rinomina con Java su Windows, con JDK semplice o con qualche libreria esterna".

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

Genera un'IOException invece di restituire un valore booleano false in caso di errore.

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


2
Inoltre, sembra che JDK 1.7 includerà un migliore supporto I / O del filesystem. Dai un'occhiata a java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC

2
JDK 1.7 non ha metodojava.nio.file.Path.moveTo()
Malte Schwerhoff

5

Nel mio caso sembrava essere un oggetto morto all'interno della mia stessa applicazione, che manteneva un handle per quel file. Quindi quella soluzione ha funzionato per me:

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

Vantaggio: è abbastanza veloce, poiché non c'è Thread.sleep () con un tempo specifico hardcoded.

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


1
Ho fatto una cosa simile, ma con una sospensione di 100 ms nel ciclo.
Lawrence Dol

4

So che sembra un po 'complicato, ma per quello che ne ho avuto bisogno, sembra che i lettori e gli scrittori bufferizzati 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, assicurati solo che oldName e newName siano percorsi completi alle posizioni dei file.

Saluti Kactus


4

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

2
Hmm, questo codice elimina srcFile anche se renameTo (o destFile.delete) non riesce e il metodo genera IOException; Non sono sicuro che sia una buona idea.
Jonik

1
@ Jonik, Grazie, corretto codice per non eliminare il file src se la ridenominazione fallisce.
Crazy Horse

Grazie per aver condiviso questo risolto il mio problema di rinomina su Windows.
BillMan

3

Su Windows uso Runtime.getRuntime().exec("cmd \\c ")e quindi uso la funzione di rinomina della riga di comando per rinominare effettivamente i file. È molto più flessibile, ad esempio se vuoi rinominare l'estensione di tutti i file txt in una directory per bak, scrivilo nel flusso 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.


Super, questo è molto meglio! Grazie! :-)
gaffcz

2

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 existingFile non esiste, ma ovviamente potrebbe essere strumentato meglio per risolvere questo problema.


2

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

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

1

Nel mio caso, l'errore era nel percorso della directory principale. 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) {
           ...

0

So che fa schifo, ma un'alternativa è creare uno script bat che emetta qualcosa di semplice come "SUCCESS" o "ERROR", invocarlo, attendere che venga eseguito e quindi controllare i suoi risultati.

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

Questo thread potrebbe essere interessante. Controlla anche la classe Process su come leggere l'output della console di un processo diverso.


-2

Puoi provare robocopy . Non si tratta esattamente di "rinominare", ma è molto affidabile.

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


Grazie. Ma poiché robocopy non è una libreria Java, probabilmente non sarebbe molto facile (raggrupparlo e) usarlo dal mio codice Java ...
Jonik

-2

Per spostare / rinominare un file puoi usare questa funzione:

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

È definito in kernel32.dll.


1
Sento che affrontare il problema di avvolgere questo in JNI è maggiore dello sforzo richiesto per avvolgere robocopy in un decoratore di processo.
Kevin Montrose

sì, questo è il prezzo che paghi per l'astrazione - e quando perde, perde bene = D
Chii

Grazie, potrei considerarlo se non diventa troppo complicato. Non ho mai usato JNI, e non poteva trovare buoni esempi di chiamare una funzione kernel di Windows su SO, così ho postato questa domanda: stackoverflow.com/questions/1000723/...
Jonik

Potresti provare un wrapper JNI generico come johannburkard.de/software/nativecall poiché si tratta di una chiamata di funzione piuttosto semplice.
Peter Smith

-8
 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.


11
Ci sono casi in cui renameTo () non funziona in modo affidabile; questo è il punto centrale della domanda.
Jonik
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.