Alternativa confiável de File.renameTo () no Windows?

92

Java File.renameTo()é problemático, especialmente no Windows, ao que parece. Como diz a documentação da API ,

Muitos aspectos do comportamento deste método são inerentemente dependentes da plataforma: A operação de renomeação pode não ser capaz de mover um arquivo de um sistema de arquivos para outro, pode não ser atômico e pode não ter sucesso se um arquivo com o nome de caminho abstrato de destino já existe. O valor de retorno deve sempre ser verificado para garantir que a operação de renomeação foi bem-sucedida.

No meu caso, como parte de um procedimento de atualização, preciso mover (renomear) um diretório que pode conter gigabytes de dados (muitos subdiretórios e arquivos de tamanhos variados). A movimentação é sempre feita na mesma partição / unidade, então não há necessidade real de mover fisicamente todos os arquivos no disco.

Não deve haver nenhum bloqueio de arquivo para o conteúdo do diretório a ser movido, mas ainda, muitas vezes, renameTo () falha em fazer seu trabalho e retorna falso. (Estou apenas supondo que talvez alguns bloqueios de arquivo expirem um tanto arbitrariamente no Windows.)

Atualmente, tenho um método alternativo que usa copiar e excluir, mas isso é uma droga porque pode demorar muito , dependendo do tamanho da pasta. Também estou pensando em simplesmente documentar o fato de que o usuário pode mover a pasta manualmente para evitar esperar horas, potencialmente. Mas o Caminho Certo obviamente seria algo automático e rápido.

Portanto, minha pergunta é: você conhece uma abordagem alternativa e confiável para fazer uma rápida mudança / renomeação com Java no Windows , seja com JDK simples ou alguma biblioteca externa. Ou se você conhece uma maneira fácil de detectar e liberar qualquer bloqueio de arquivo para uma determinada pasta e todo o seu conteúdo (possivelmente milhares de arquivos individuais), também não há problema.


Edit : Neste caso específico, parece que escapamos usando apenas renameTo()levando mais algumas coisas em consideração; veja esta resposta .

Jonik
fonte
3
Você pode esperar / usar o JDK 7, que tem um suporte de sistema de arquivos muito melhor.
akarnokd
@ kd304, na verdade mal posso esperar ou usar uma versão de acesso antecipado, mas é interessante saber que algo assim está a caminho!
Jonik

Respostas:

52

Veja também o Files.move()método em JDK 7.

Um exemplo:

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
fonte
7
infelizmente, Java7 nem sempre é a resposta (como 42 é)
wuppi
1
Mesmo no ubuntu, JDK7, enfrentamos esse problema ao executar o código no EC2 com armazenamento EBS. File.renameTo falhou, assim como File.canWrite.
saurabheights
Lembre-se de que isso é tão confiável quanto File # renameTo (). Ele apenas fornece um erro mais útil quando falha. A única maneira razoavelmente confiável que encontrei é copiar o arquivo com Arquivos # copiar para o novo nome e, em seguida, excluir o original usando Arquivos # excluir (a própria exclusão também pode falhar, pela mesma razão que Arquivos # mover pode falhar) .
partindo de
26

Para valer a pena, mais algumas noções:

  1. No Windows, renameTo()parece falhar se o diretório de destino existir, mesmo se estiver vazio. Isso me surpreendeu, pois já havia tentado no Linux, onde dava renameTo()certo se o target existisse, desde que estivesse vazio.

    (Obviamente, eu não deveria ter assumido que esse tipo de coisa funciona da mesma forma em todas as plataformas; é exatamente sobre isso que o Javadoc avisa.)

  2. Se você suspeitar que pode haver alguns bloqueios de arquivo persistentes, esperar um pouco antes de mover / renomear pode ajudar. (Em um ponto em nosso instalador / atualizador, adicionamos uma ação de "suspensão" e uma barra de progresso indeterminado por cerca de 10 segundos, porque pode haver um serviço pendurado em alguns arquivos). Talvez até mesmo faça um mecanismo de nova tentativa simples que tenta renameTo()e depois espera por um período (que pode aumentar gradualmente), até que a operação seja bem-sucedida ou algum tempo limite seja atingido.

No meu caso, a maioria dos problemas parece ter sido resolvida levando-se ambos os itens acima em consideração, então não precisaremos fazer uma chamada de kernel nativo, ou algo parecido, afinal.

Jonik
fonte
2
Estou aceitando minha própria resposta por enquanto, pois descreve o que ajudou em nosso caso. Ainda assim, se alguém vier com uma boa resposta para a questão mais geral com renameTo (), fique à vontade para postar e terei o maior prazer em reconsiderar a resposta aceita.
Jonik
4
6,5 anos depois, acho que é hora de aceitar a resposta do JDK 7 , especialmente porque muitas pessoas a consideram útil. =)
Jonik
19

A postagem original solicitava "uma abordagem alternativa e confiável para mover / renomear rapidamente com Java no Windows, seja com JDK simples ou alguma biblioteca externa".

Outra opção não mencionada ainda aqui é v1.3.2 ou posterior do biblioteca apache.commons.io , que inclui FileUtils.moveFile () .

Ele lança uma IOException em vez de retornar falso booleano em caso de erro.

Veja também a resposta de big lep neste outro tópico .

MykennaC
fonte
2
Além disso, parece que o JDK 1.7 incluirá melhor suporte para E / S de sistema de arquivos. Confira java.nio.file.Path.moveTo (): java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC
2
JDK 1.7 não tem métodojava.nio.file.Path.moveTo()
Malte Schwerhoff
5

No meu caso, parecia ser um objeto morto em meu próprio aplicativo, que mantinha um identificador para esse arquivo. Então, essa solução funcionou para mim:

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

Vantagem: é muito rápido, pois não há Thread.sleep () com um tempo codificado específico.

Desvantagem: esse limite de 20 é algum número codificado. Em todos os meus testes, i = 1 é suficiente. Mas, para ter certeza, deixei em 20.

wuppi
fonte
1
Fiz algo parecido, mas com um sono de 100ms no loop.
Lawrence Dol
4

Eu sei que isso parece um pouco hackeado, mas para o que estou precisando, parece que leitores e gravadores em buffer não têm problemas para criar os arquivos.

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 bem para pequenos arquivos de texto como parte de um analisador, apenas certifique-se de que oldName e newName sejam caminhos completos para os locais dos arquivos.

Cheers Kactus

Kactus
fonte
4

O código a seguir NÃO é uma 'alternativa', mas funcionou de maneira confiável para mim em ambientes 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();
          }
    }
}
4 rotações
fonte
2
Hmm, este código exclui srcFile mesmo se renameTo (ou destFile.delete) falhar e o método lançar IOException; Não tenho certeza se isso é uma boa ideia.
Jonik de
1
@Jonik, Thanx, corrigiu o código para não excluir o arquivo src se a renomeação falhar.
cavalo louco,
Obrigado por compartilhar isso corrigiu meu problema de renomeação no Windows.
BillMan de
3

No windows eu uso Runtime.getRuntime().exec("cmd \\c ")e, em seguida, uso a função de renomeação da linha de comando para realmente renomear arquivos. É muito mais flexível, por exemplo, se você quiser renomear a extensão de todos os arquivos txt em um diretório para bak, basta escrever no fluxo de saída:

renomear * .txt * .bak

Eu sei que não é uma boa solução, mas aparentemente sempre funcionou para mim, muito melhor do que o suporte embutido Java.

Johnydep
fonte
Super, isso é muito melhor! Obrigado! :-)
gaffcz
2

Por que não....

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 no nwindows 7, não faz nada se existingFile não existir, mas obviamente poderia ser melhor instrumentado para corrigir isso.

bagagem
fonte
2

Eu tive uma questão semelhante. O arquivo foi copiado em vez de mover no Windows, mas funcionou bem no Linux. Corrigi o problema fechando o fileInputStream aberto antes de chamar renameTo (). Testado em Windows XP.

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

No meu caso, o erro estava no caminho do diretório pai. Talvez um bug, tive que usar a substring para obter um caminho correto.

        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
fonte
0

Eu sei que é uma merda, mas uma alternativa é criar um script bat que produza algo simples como "SUCCESS" ou "ERROR", invoque-o, espere que seja executado e verifique seus resultados.

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

Este tópico pode ser interessante. Verifique também a classe Process para saber como ler a saída do console de um processo diferente.

Ravi Wallau
fonte
-2

Você pode tentar o robocopy . Isso não é exatamente "renomear", mas é muito confiável.

Robocopy é projetado para espelhamento confiável de diretórios ou árvores de diretórios. Ele tem recursos para garantir que todos os atributos e propriedades do NTFS sejam copiados e inclui código de reinicialização adicional para conexões de rede sujeitas a interrupção.

Anton Gogolev
fonte
Obrigado. Mas como o robocopy não é uma biblioteca Java, provavelmente não seria muito fácil (empacotá-lo e) usá-lo a partir do meu código Java ...
Jonik
-2

Para mover / renomear um arquivo, você pode usar esta função:

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

Ele é definido em kernel32.dll.

Cego
fonte
1
Acho que o trabalho de embrulhar isso em JNI é maior do que o esforço necessário para embrulhar o robocopy em um decorador de processo.
Kevin Montrose
Sim, este é o preço que você paga pela abstração - e quando vaza, vaza bem = D
Chii
Obrigado, posso considerar isso se não ficar muito complicado. Nunca usei o JNI e não consegui encontrar bons exemplos de como chamar uma função do kernel do Windows no SO, então postei esta pergunta: stackoverflow.com/questions/1000723/…
Jonik
Você pode tentar um wrapper JNI genérico como johannburkard.de/software/nativecall, pois esta é uma chamada de função muito simples.
Peter Smith
-8
 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

O código acima é simples. Eu testei no windows 7 e funciona perfeitamente bem.

iltaf khalid
fonte
11
Há casos em que renameTo () não funciona de maneira confiável; esse é o ponto principal da questão.
Jonik