¿Alternativa confiable File.renameTo () en Windows?

El File.renameTo() Java es problemático, especialmente en Windows, parece. Como dice la documentación de API ,

Muchos aspectos del comportamiento de este método dependen intrínsecamente de la plataforma: la operación de cambio de nombre podría no ser capaz de mover un archivo de un sistema de archivos a otro, podría no ser atómico y podría no tener éxito si un archivo con el nombre de ruta abstracto de destino ya existe. El valor de retorno siempre se debe verificar para asegurarse de que la operación de cambio de nombre fue exitosa.

En mi caso, como parte de un procedimiento de actualización, necesito mover (renombrar) un directorio que puede contener gigabytes de datos (muchos subdirectorios y archivos de diferentes tamaños). El movimiento siempre se realiza dentro de la misma partición / unidad, por lo que no es realmente necesario mover físicamente todos los archivos en el disco.

No debe haber ningún locking de archivos en el contenido del directorio para moverlo, pero aún así, muy a menudo, renameTo () no hace su trabajo y devuelve falso. (Estoy adivinando que tal vez algunos lockings de archivos caduquen un tanto arbitrariamente en Windows).

Actualmente tengo un método alternativo que usa copiar y eliminar, pero esto apesta porque puede tomar mucho tiempo, dependiendo del tamaño de la carpeta. También estoy considerando simplemente documentar el hecho de que el usuario puede mover la carpeta manualmente para evitar esperar horas, potencialmente. Pero el Camino Correcto obviamente sería algo automático y rápido.

Así que mi pregunta es, ¿conocen un enfoque alternativo y confiable para hacer un movimiento / cambio de nombre rápido con Java en Windows , ya sea con JDK simple o alguna biblioteca externa? O si conoce una manera fácil de detectar y liberar cualquier locking de archivos para una carpeta determinada y todos sus contenidos (posiblemente miles de archivos individuales), eso también estaría bien.


Editar : en este caso particular, parece que hemos escapado utilizando solo renameTo() teniendo en cuenta algunas cosas más; ver esta respuesta

Ver también el método Files.move() en JDK 7.

Un ejemplo:

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

Por lo que vale, algunas nociones adicionales:

  1. En Windows, renameTo() parece fallar si el directorio de destino existe, incluso si está vacío. Esto me sorprendió, como lo había intentado con Linux, donde renameTo() tuvo éxito si existía un objective, siempre que estuviera vacío.

    (Obviamente no debería haber asumido que este tipo de cosas funciona igual en todas las plataformas, esto es exactamente lo que el Javadoc advierte).

  2. Si sospecha que puede haber algunos lockings de archivos persistentes, esperar un poco antes de mover / cambiar el nombre podría ayudar. (En un punto de nuestro instalador / mejorador agregamos una acción de “suspensión” y una barra de progreso indeterminada durante unos 10 segundos, porque puede haber un servicio que cuelga de algunos archivos). Tal vez incluso haga un simple mecanismo de rebash que intente renameTo() , y luego espere un período (que tal vez aumente gradualmente), hasta que la operación tenga éxito o se scope algún tiempo de espera.

En mi caso, la mayoría de los problemas parecen haberse resuelto teniendo en cuenta los dos puntos anteriores, así que no necesitaremos hacer una llamada nativa al núcleo, o algo así, después de todo.

La publicación original solicitó “un enfoque alternativo y confiable para hacer un movimiento / cambio de nombre rápido con Java en Windows, ya sea con JDK simple o alguna biblioteca externa”.

Otra opción aún no mencionada aquí es v1.3.2 o posterior de la biblioteca apache.commons.io , que incluye FileUtils.moveFile () .

Lanza una IOException en lugar de devolver boolean false al error.

Ver también la respuesta de big lep en este otro hilo .

La siguiente pieza de código NO es una ‘alternativa’, pero me ha funcionado de manera confiable tanto en entornos Windows como 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(); } } } 

En mi caso, parecía ser un objeto muerto dentro de mi propia aplicación, que mantenía un control sobre ese archivo. Entonces esa solución funcionó para mí:

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

Ventaja: es bastante rápido, ya que no hay Thread.sleep () con un tiempo codificado específico.

Desventaja: ese límite de 20 es un número codificado. En todas mis pruebas, i = 1 es suficiente. Pero para estar seguro lo dejé en 20.

Sé que esto parece un poco raro, pero por lo que he estado necesitando, parece que los lectores y escritores protegidos no tienen problemas para crear los archivos.

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

Funciona bien para archivos de texto pequeños como parte de un analizador, solo asegúrese de que oldName y newName sean rutas completas a las ubicaciones de archivos.

Saludos Kactus

En Windows utilizo Runtime.getRuntime().exec("cmd \\c ") y luego uso la función de cambio de nombre de línea de comando para cambiar el nombre de los archivos. Es mucho más flexible, por ejemplo, si desea cambiar el nombre de la extensión de todos los archivos txt en un directorio a bak, simplemente escríbalo en el flujo de salida:

cambiar el nombre * .txt * .bak

Sé que no es una buena solución, pero aparentemente siempre me ha funcionado, mucho mejor que Java en línea.

Por qué 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); } } 

funciona en nwindows 7, no hace nada si existingFile no existe, pero obviamente podría estar mejor instrumentado para solucionarlo.

En mi caso, el error estaba en la ruta del directorio principal. Tal vez un error, tuve que usar la subcadena para obtener un camino correcto.

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

Tuve un problema similar. El archivo se copió bastante en movimiento en Windows, pero funcionó bien en Linux. Solucioné el problema cerrando el fileInputStream abierto antes de llamar a renameTo (). Probado en Windows XP.

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

Sé que apesta, pero una alternativa es crear un script bat que genere algo simple como “SUCCESS” o “ERROR”, invocarlo, esperar a que se ejecute y luego verificar sus resultados.

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

Este hilo puede ser interesante. Consulte también la clase de proceso sobre cómo leer la salida de la consola de un proceso diferente.

Puede probar robocopy . Esto no es exactamente “cambio de nombre”, pero es muy confiable.

Robocopy está diseñado para la duplicación fiable de directorios o árboles de directorios. Tiene características para garantizar que todos los atributos y propiedades NTFS se copien e incluye un código de reinicio adicional para las conexiones de red sujetas a interrupción.

Para mover / cambiar el nombre de un archivo puede usar esta función:

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

Está definido en kernel32.dll.

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

Lo anterior es el código simple. He probado en Windows 7 y funciona perfectamente bien.