Maneira concisa padrão para copiar um arquivo em Java?

Sempre me incomodou que a única maneira de copiar um arquivo em Java envolve a abertura de streams, a declaração de um buffer, a leitura em um arquivo, o looping e a gravação no outro arquivo. A web está repleta de implementações semelhantes, mas ainda ligeiramente diferentes deste tipo de solução.

Existe uma maneira melhor de permanecer dentro dos limites da linguagem Java (o significado não envolve a execução de comandos específicos do SO)? Talvez em algum pacote confiável de software de fonte aberta, isso pudesse pelo menos obscurecer essa implementação subjacente e fornecer uma solução de uma linha?

Como mencionado acima, o Apache Commons IO é o caminho a seguir, especificamente o FileUtils . copyFile () ; Ele lida com todo o trabalho pesado para você.

E como um postscript, observe que versões recentes do FileUtils (como a versão 2.0.1) adicionaram o uso do NIO para copiar arquivos; O NIO pode aumentar significativamente o desempenho da cópia de arquivos , em grande parte porque as rotinas do NIO adiam a cópia diretamente no sistema operacional / sistema de arquivos, em vez de manipulá-lo lendo e gravando bytes através da camada Java. Portanto, se você estiver procurando desempenho, talvez valha a pena verificar se você está usando uma versão recente do FileUtils.

Eu evitaria o uso de um mega api como o apache commons. Esta é uma operação simplista e é incorporada ao JDK no novo pacote NIO. Foi uma espécie de link já em uma resposta anterior, mas o método-chave na API NIO são as novas funções “transferTo” e “transferFrom”.

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html#transferTo(long,%20long,%20java.nio.channels.WritableByteChannel)

Um dos artigos vinculados mostra uma ótima maneira de integrar essa function em seu código, usando a transferência de:

public static void copyFile(File sourceFile, File destFile) throws IOException { if(!destFile.exists()) { destFile.createNewFile(); } FileChannel source = null; FileChannel destination = null; try { source = new FileInputStream(sourceFile).getChannel(); destination = new FileOutputStream(destFile).getChannel(); destination.transferFrom(source, 0, source.size()); } finally { if(source != null) { source.close(); } if(destination != null) { destination.close(); } } } 

Aprender NIO pode ser um pouco complicado, então você pode confiar nesse mecanismo antes de tentar aprender NIO durante a noite. Por experiência pessoal, pode ser muito difícil aprender se você não tem experiência e foi introduzido no IO através dos streams java.io.

Agora, com o Java 7, você pode usar a seguinte syntax try-with-resource:

 public static void copyFile( File from, File to ) throws IOException { if ( !to.exists() ) { to.createNewFile(); } try ( FileChannel in = new FileInputStream( from ).getChannel(); FileChannel out = new FileOutputStream( to ).getChannel() ) { out.transferFrom( in, 0, in.size() ); } } 

Ou, melhor ainda, isso também pode ser feito usando a nova class Files introduzida no Java 7:

 public static void copyFile( File from, File to ) throws IOException { Files.copy( from.toPath(), to.toPath() ); } 

Muito elegante, né?

  • Esses methods são projetados por engenharia de desempenho (eles se integram com a E / S nativa do sistema operacional).
  • Esses methods funcionam com arquivos, diretórios e links.
  • Cada uma das opções fornecidas pode ser deixada de fora – elas são opcionais.

A class de utilidade

 package com.yourcompany.nio; class Files { static int copyRecursive(Path source, Path target, boolean prompt, CopyOptions options...) { CopyVisitor copyVisitor = new CopyVisitor(source, target, options).copy(); EnumSet fileVisitOpts; if (Arrays.toList(options).contains(java.nio.file.LinkOption.NOFOLLOW_LINKS) { fileVisitOpts = EnumSet.noneOf(FileVisitOption.class) } else { fileVisitOpts = EnumSet.of(FileVisitOption.FOLLOW_LINKS); } Files.walkFileTree(source[i], fileVisitOpts, Integer.MAX_VALUE, copyVisitor); } private class CopyVisitor implements FileVisitor { final Path source; final Path target; final CopyOptions[] options; CopyVisitor(Path source, Path target, CopyOptions options...) { this.source = source; this.target = target; this.options = options; }; @Override FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { // before visiting entries in a directory we copy the directory // (okay if directory already exists). Path newdir = target.resolve(source.relativize(dir)); try { Files.copy(dir, newdir, options); } catch (FileAlreadyExistsException x) { // ignore } catch (IOException x) { System.err.format("Unable to create: %s: %s%n", newdir, x); return SKIP_SUBTREE; } return CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { Path newfile= target.resolve(source.relativize(file)); try { Files.copy(file, newfile, options); } catch (IOException x) { System.err.format("Unable to copy: %s: %s%n", source, x); } return CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) { // fix up modification time of directory when done if (exc == null && Arrays.toList(options).contains(COPY_ATTRIBUTES)) { Path newdir = target.resolve(source.relativize(dir)); try { FileTime time = Files.getLastModifiedTime(dir); Files.setLastModifiedTime(newdir, time); } catch (IOException x) { System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x); } } return CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { if (exc instanceof FileSystemLoopException) { System.err.println("cycle detected: " + file); } else { System.err.format("Unable to copy: %s: %s%n", file, exc); } return CONTINUE; } } 

Copiando um diretório ou arquivo

 long bytes = java.nio.file.Files.copy( new java.io.File("").toPath(), new java.io.File("").toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES, java.nio.file.LinkOption.NOFOLLOW_LINKS); 

Movendo um diretório ou arquivo

 long bytes = java.nio.file.Files.move( new java.io.File("").toPath(), new java.io.File("").toPath(), java.nio.file.StandardCopyOption.ATOMIC_MOVE, java.nio.file.StandardCopyOption.REPLACE_EXISTING); 

Copiando um diretório ou arquivo recursivamente

 long bytes = com.yourcompany.nio.Files.copyRecursive( new java.io.File("").toPath(), new java.io.File("").toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES java.nio.file.LinkOption.NOFOLLOW_LINKS ); 

No Java 7 é fácil …

 File src = new File("original.txt"); File target = new File("copy.txt"); Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); 

Para copiar um arquivo e salvá-lo no caminho de destino, você pode usar o método abaixo.

 public void copy(File src, File dst) throws IOException { InputStream in = new FileInputStream(src); try { OutputStream out = new FileOutputStream(dst); try { // Transfer bytes from in to out byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } finally { out.close(); } } finally { in.close(); } } 

Observe que todos esses mecanismos apenas copiam o conteúdo do arquivo, não os metadados, como permissions. Então, se você copiasse ou movesse um arquivo .sh executável no linux, o novo arquivo não seria executável.

Para realmente copiar ou mover um arquivo, ou seja, obter o mesmo resultado que copiar de uma linha de comando, você realmente precisa usar uma ferramenta nativa. Um script de shell ou JNI.

Aparentemente, isso pode ser corrigido no java 7 – http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Dedos cruzados!

A biblioteca de goiabas do Google também possui um método de cópia :

 cópia void estática pública ( File from,
                         Arquivar para)
                  lança IOException 
Copia todos os bytes de um arquivo para outro.

Aviso: Se to representar um arquivo existente, esse arquivo será sobrescrito com o conteúdo de. Se e from referirem ao mesmo arquivo, o conteúdo desse arquivo será excluído.

parameters: from – o arquivo de origem to – o arquivo de destino

Lança: IOException – se ocorrer um erro de E / S IllegalArgumentException – if from.equals(to)

Disponível como padrão no Java 7, path.copyTo: http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/ tutorial / essential / io / copy.html

Eu não posso acreditar que demorou tanto tempo para padronizar algo tão comum e simples como copiar arquivos 🙁

Três possíveis problemas com o código acima:

  1. Se getChannel lançar uma exceção, você poderá vazar um stream aberto.
  2. Para arquivos grandes, você pode estar tentando transferir mais de uma vez do que o sistema operacional pode manipular.
  3. Você está ignorando o valor de retorno de transferFrom, portanto, pode estar copiando apenas parte do arquivo.

É por isso que org.apache.tools.ant.util.ResourceUtils.copyResource é tão complicado. Observe também que enquanto transferFrom estiver OK, transferTo interrompe o JDK 1.4 no Linux (consulte ID do bug: 5056395 ) – Jesse Glick Jan

Se você estiver em um aplicativo da web que já usa Spring e não quiser include o Apache Commons IO para copiar arquivos simples, use o FileCopyUtils da estrutura Spring.

Aqui estão três maneiras que você pode facilmente copiar arquivos com uma única linha de código!

Java7 :

java.nio.file.Files # copy

 private static void copyFileUsingJava7Files(File source, File dest) throws IOException { Files.copy(source.toPath(), dest.toPath()); } 

Appache Commons IO :

FileUtils # copyFile

 private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException { FileUtils.copyFile(source, dest); } 

Goiaba :

Cópia dos arquivos #

 private static void copyFileUsingGuava(File source,File dest) throws IOException{ Files.copy(source,dest); } 
 public static void copyFile(File src, File dst) throws IOException { long p = 0, dp, size; FileChannel in = null, out = null; try { if (!dst.exists()) dst.createNewFile(); in = new FileInputStream(src).getChannel(); out = new FileOutputStream(dst).getChannel(); size = in.size(); while ((dp = out.transferFrom(in, p, size)) > 0) { p += dp; } } finally { try { if (out != null) out.close(); } finally { if (in != null) in.close(); } } } 

Rápido e trabalhe com todas as versões do Java também Android:

 private void copy(final File f1, final File f2) throws IOException { f2.createNewFile(); final RandomAccessFile file1 = new RandomAccessFile(f1, "r"); final RandomAccessFile file2 = new RandomAccessFile(f2, "rw"); file2.getChannel().write(file1.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f1.length())); file1.close(); file2.close(); } 

A cópia NIO com um buffer é a mais rápida de acordo com o meu teste. Veja o código de trabalho abaixo de um projeto de teste meu em https://github.com/mhisoft/fastcopy

 import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.text.DecimalFormat; public class test { private static final int BUFFER = 4096*16; static final DecimalFormat df = new DecimalFormat("#,###.##"); public static void nioBufferCopy(final File source, final File target ) { FileChannel in = null; FileChannel out = null; double size=0; long overallT1 = System.currentTimeMillis(); try { in = new FileInputStream(source).getChannel(); out = new FileOutputStream(target).getChannel(); size = in.size(); double size2InKB = size / 1024 ; ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER); while (in.read(buffer) != -1) { buffer.flip(); while(buffer.hasRemaining()){ out.write(buffer); } buffer.clear(); } long overallT2 = System.currentTimeMillis(); System.out.println(String.format("Copied %s KB in %s millisecs", df.format(size2InKB), (overallT2 - overallT1))); } catch (IOException e) { e.printStackTrace(); } finally { close(in); close(out); } } private static void close(Closeable closable) { if (closable != null) { try { closable.close(); } catch (IOException e) { if (FastCopy.debug) e.printStackTrace(); } } } 

}

Um pouco atrasado para a festa, mas aqui está uma comparação do tempo gasto para copiar um arquivo usando vários methods de cópia de arquivo. Eu entrei através dos methods por 10 vezes e tirei uma média. A transferência de arquivos usando streams de E / S parece ser o pior candidato:

Comparação de transferência de arquivos usando vários métodos

Aqui estão os methods:

 private static long fileCopyUsingFileStreams(File fileToCopy, File newFile) throws IOException { FileInputStream input = new FileInputStream(fileToCopy); FileOutputStream output = new FileOutputStream(newFile); byte[] buf = new byte[1024]; int bytesRead; long start = System.currentTimeMillis(); while ((bytesRead = input.read(buf)) > 0) { output.write(buf, 0, bytesRead); } long end = System.currentTimeMillis(); input.close(); output.close(); return (end-start); } private static long fileCopyUsingNIOChannelClass(File fileToCopy, File newFile) throws IOException { FileInputStream inputStream = new FileInputStream(fileToCopy); FileChannel inChannel = inputStream.getChannel(); FileOutputStream outputStream = new FileOutputStream(newFile); FileChannel outChannel = outputStream.getChannel(); long start = System.currentTimeMillis(); inChannel.transferTo(0, fileToCopy.length(), outChannel); long end = System.currentTimeMillis(); inputStream.close(); outputStream.close(); return (end-start); } private static long fileCopyUsingApacheCommons(File fileToCopy, File newFile) throws IOException { long start = System.currentTimeMillis(); FileUtils.copyFile(fileToCopy, newFile); long end = System.currentTimeMillis(); return (end-start); } private static long fileCopyUsingNIOFilesClass(File fileToCopy, File newFile) throws IOException { Path source = Paths.get(fileToCopy.getPath()); Path destination = Paths.get(newFile.getPath()); long start = System.currentTimeMillis(); Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); long end = System.currentTimeMillis(); return (end-start); } 

A única desvantagem que eu posso ver ao usar a class de canal NIO é que eu ainda não consigo encontrar uma maneira de mostrar o progresso da cópia intermediária de arquivos.