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

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

Muitos aspectos do comportamento desse 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ômica e pode não ser bem-sucedida 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 seja bem-sucedida.

No meu caso, como parte de um procedimento de upgrade, preciso mover (renomear) um diretório que possa conter gigabytes de dados (muitos subdiretórios e arquivos de tamanhos variados). A movimentação é sempre feita na mesma partição / unidade, portanto, 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 assim, muitas vezes, renameTo () não faz seu trabalho e retorna false. (Estou apenas supondo que talvez alguns bloqueios de arquivos expirem arbitrariamente no Windows.)

Atualmente eu tenho um método de fallback que usa copiar e excluir, mas isso é uma droga porque pode levar muito tempo, dependendo do tamanho da pasta. Eu também estou considerando simplesmente documentar o fato de que o usuário pode mover a pasta manualmente para evitar a espera por horas, potencialmente. Mas o Caminho Certo, obviamente, seria algo automático e rápido.

Então, minha pergunta é: você conhece uma abordagem alternativa e confiável para fazer uma mudança rápida / renomear com Java no Windows , seja com o JDK simples ou com 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), isso também seria bom.


Edit : Neste caso particular, parece que nos renameTo() usando apenas renameTo() , levando mais algumas coisas em conta; veja esta resposta .

Veja também o método Files.move() no 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); } 

Por que vale a pena, algumas noções adicionais:

  1. No Windows, renameTo() parece falhar se o diretório de destino existir, mesmo se estiver vazio. Isso me surpreendeu, como eu tentei no Linux, onde renameTo() sucesso se o alvo existisse, desde que estivesse vazio.

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

  2. Se você suspeitar que pode haver alguns bloqueios de arquivos remanescentes, esperar um pouco antes da mudança / renomeação pode ajudar. (Em um ponto em nosso instalador / atualizador, adicionamos uma ação “sleep” e uma barra de progresso indeterminada por cerca de 10 segundos, porque pode haver um serviço pendurado em alguns arquivos). Talvez até mesmo fazer um mecanismo de repetição simples que tente renameTo() e, em seguida, aguarde um período (que talvez aumente 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 em consideração os dois aspectos acima, portanto, não precisaremos fazer uma chamada do kernel nativo, ou algo assim, afinal de contas.

A postagem original solicitou “uma abordagem alternativa e confiável para fazer uma mudança rápida / renomear com o Java no Windows, seja com o JDK simples ou com alguma biblioteca externa”.

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

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

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

O seguinte trecho de código NÃO é uma ‘alternativa’, mas funcionou de forma 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(); } } } 

No meu caso, parecia ser um object morto dentro do 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: é bem rápido, já que não há Thread.sleep () com um tempo específico codificado.

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

Eu sei que isso parece um pouco hacky, mas para o que eu estou precisando, parece leitores e gravadores de buffer não têm nenhum problema para fazer 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.

Felicidades Kactus

No windows eu uso Runtime.getRuntime().exec("cmd \\c ") e, em seguida, uso a function de renomear a 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 dir para bak, apenas escreva isto no stream 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 inline Java.

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

No meu caso, o erro estava no caminho do diretório pai. Talvez um bug, eu 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) { ... 

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

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

Eu sei que é uma porcaria, mas uma alternativa é criar um script de morcego que produza algo simples como “SUCESSO” ou “ERRO”, invoque-o, espere que ele seja executado e, em seguida, verifique seus resultados.

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

Esse segmento pode ser interessante. Verifique também a class Process sobre como ler a saída do console de um processo diferente.

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

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

Para mover / renomear um arquivo, você pode usar esta function:

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

Está definido no kernel32.dll.

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

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