¿Alternativa confiable de File.renameTo () en Windows?

92

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

Muchos aspectos del comportamiento de este método dependen inherentemente de la plataforma: es posible que la operación de cambio de nombre no pueda mover un archivo de un sistema de archivos a otro, que no sea atómico y que no tenga éxito si un archivo con el nombre de ruta abstracto de destino ya existe. Siempre se debe verificar el valor de retorno 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 hay una necesidad real de mover físicamente todos los archivos en el disco.

No debería haber ningún archivo bloqueado en el contenido del directorio que se va a mover, pero aún así, con bastante frecuencia, renameTo () no hace su trabajo y devuelve falso. (Solo supongo que quizás algunos bloqueos de archivos caducan de forma algo arbitraria en Windows).

Actualmente tengo un método alternativo que usa copiar y eliminar, pero esto apesta porque puede llevar 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.

Entonces, mi pregunta es, ¿conoce un enfoque alternativo y confiable para hacer un movimiento rápido / cambio de nombre 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 bloqueo de archivo para una carpeta determinada y todo su contenido (posiblemente miles de archivos individuales), también estaría bien.


Editar : En este caso particular, parece que nos salimos con la suya simplemente renameTo()teniendo en cuenta algunas cosas más; ver esta respuesta .

Jonik
fuente
3
Puede esperar / usar JDK 7, que tiene un soporte de sistema de archivos mucho mejor.
akarnokd
@ kd304, en realidad no puedo esperar ni usar una versión de acceso anticipado, ¡pero es interesante saber que algo así está en camino!
Jonik

Respuestas:

52

Consulte también el Files.move()método 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);
}
Alan
fuente
7
desafortunadamente, Java7 no siempre es la respuesta (como 42 es)
wuppi
1
Incluso en ubuntu, JDK7, enfrentamos este problema al ejecutar código en EC2 con almacenamiento EBS. File.renameTo falló y también lo hizo File.canWrite.
saurabheights
Tenga en cuenta que esto es tan poco confiable como File # renameTo (). Simplemente da un error más útil cuando falla. La única forma razonablemente confiable que he encontrado es copiar el archivo con Files # copy al nuevo nombre, y luego eliminar el original usando Files # delete (que la eliminación también puede fallar, por la misma razón que Files # move puede fallar) .
Jwenting
26

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ó, ya que lo había probado en Linux, donde tenía renameTo()éxito si el objetivo existía, 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 sobre lo que advierte el Javadoc).

  2. Si sospecha que puede haber algunos bloqueos de archivos persistentes, esperar un poco antes de mover / cambiar el nombre puede ayudar. (En un punto de nuestro instalador / actualizador agregamos una acción de "suspensión" y una barra de progreso indeterminada durante unos 10 segundos, porque podría haber un servicio colgando de algunos archivos). Quizás incluso haga un mecanismo de reintento simple que lo intente renameTo()y luego espere un período (que tal vez aumente gradualmente), hasta que la operación sea exitosa o se alcance algún tiempo de espera.

En mi caso, la mayoría de los problemas parecen haberse resuelto teniendo en cuenta ambos aspectos anteriores, por lo que, después de todo, no necesitaremos hacer una llamada nativa al kernel o algo por el estilo.

Jonik
fuente
2
Estoy aceptando mi propia respuesta por ahora, ya que describe lo que ayudó en nuestro caso. Aún así, si a alguien se le ocurre una excelente respuesta al problema más general con renameTo (), no dude en publicar y estaré feliz de reconsiderar la respuesta aceptada.
Jonik
4
6.5 años después, creo que es hora de aceptar la respuesta del JDK 7 , especialmente porque mucha gente la considera útil. =)
Jonik
19

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

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

Lanza una IOException en lugar de devolver un booleano falso en caso de error.

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

MykennaC
fuente
2
Además, parece que JDK 1.7 incluirá un mejor soporte de E / S del sistema de archivos. Consulte java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 no tiene métodojava.nio.file.Path.moveTo()
Malte Schwerhoff
5

En mi caso, parecía ser un objeto muerto dentro de mi propia aplicación, que mantenía un identificador para 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é a las 20.

wuppi
fuente
1
Hice algo similar, pero con un sueño de 100 ms en el bucle.
Lawrence Dol
4

Sé que esto parece un poco complicado, pero para lo que lo he estado necesitando, parece que los lectores y escritores almacenados en búfer 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 los archivos.

Saludos Kactus

Kactus
fuente
4

El siguiente fragmento 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();
          }
    }
}
caballo loco
fuente
2
Hmm, este código elimina srcFile incluso si renameTo (o destFile.delete) falla y el método arroja IOException; No estoy seguro de si es una buena idea.
Jonik
1
@Jonik, Thanx, código fijo para no eliminar el archivo src si el cambio de nombre falla.
caballo loco
Gracias por compartir esto solucionó mi problema de cambio de nombre en Windows.
BillMan
3

En Windows, uso Runtime.getRuntime().exec("cmd \\c ")y luego uso la función de cambio de nombre de la línea de comandos 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 escriba esto en el flujo de salida:

renombrar * .txt * .bak

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

Johnydep
fuente
¡Super, esto es mucho mejor! ¡Gracias! :-)
gaffcz
2

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 solucionar esto.

caja
fuente
2

Tuve un problema similar. El archivo se copió en lugar de moverse 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);
Tharaka
fuente
1

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

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

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 resultar interesante. Consulte también la clase Process sobre cómo leer la salida de la consola de un proceso diferente.

Ravi Wallau
fuente
-2

Puede intentar robocopy . Esto no es exactamente un "cambio de nombre", pero es muy confiable.

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

Anton Gogolev
fuente
Gracias. Pero dado que robocopy no es una biblioteca de Java, probablemente no sería muy fácil (empaquetarlo y) usarlo desde mi código Java ...
Jonik
-2

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

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

Está definido en kernel32.dll.

Blindy
fuente
1
Siento que pasar por la molestia de envolver esto en JNI es mayor que el esfuerzo requerido para envolver robocopy en un decorador de procesos.
Kevin Montrose
sí, este es el precio que paga por la abstracción, y cuando se filtra, se filtra bien = D
Chii
Gracias, podría considerar esto si no se vuelve demasiado complicado. Nunca he usado JNI y no pude encontrar buenos ejemplos de
cómo
Puede probar un contenedor JNI genérico como johannburkard.de/software/nativecall ya que es una llamada de función bastante simple.
Peter Smith
-8
 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.

iltaf khalid
fuente
11
Hay casos en los que renameTo () no funciona de manera confiable; ese es el punto de la pregunta.
Jonik