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

421

Sempre me incomodou que a única maneira de copiar um arquivo em Java envolvesse a abertura de fluxos, a declaração de um buffer, a leitura em um arquivo, o looping e a gravação no outro vapor. A web está repleta de implementações semelhantes, ainda que ligeiramente diferentes, desse 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 de utilitários de código aberto confiável, isso oculte pelo menos essa implementação subjacente e forneça uma solução de uma linha?

Pedro
fonte
5
Pode haver algo no Apache Commons FileUtils , especificamente, nos métodos copyFile .
toolkit
22
Se estiver usando Java 7, use Files.copy em vez disso, como recomendado pelo @GlenBest: stackoverflow.com/a/16600787/44737
Rob

Respostas:

274

Como o kit de ferramentas menciona 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 as 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 para o sistema operacional / sistema de arquivos, em vez de lidar com isso, lendo e gravando bytes na camada Java. Portanto, se você está procurando desempenho, pode valer a pena verificar se está usando uma versão recente do FileUtils.

delfuego
fonte
1
Muito útil - você tem alguma idéia de quando um lançamento oficial incorporará essas alterações?
Peter
2
Lançamento público do Apache Commons IO ainda em 1.4, grrrrrrr
Peter
14
Em dezembro de 2010, o Apache Commons IO estava na 2.0.1, que possui a funcionalidade NIO. Resposta atualizada.
Simon Nickerson
4
Um aviso para as pessoas Android: Isso não está incluído nas APIs do Android padrão
IlDan
18
Se estiver usando Java 7 ou mais recente, você pode usar Files.copy como sugerido por @GlenBest: stackoverflow.com/a/16600787/44737
Rob
278

Eu evitaria o uso de uma mega api como apache commons. Essa é uma operação simplista e incorporada ao JDK no novo pacote NIO. Ele já estava vinculado em uma resposta anterior, mas o método principal na API do 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 função ao seu código, usando o transferFrom:

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 apenas nesse mecânico antes de sair e tentar aprender NIO da noite para o dia. Pela experiência pessoal, pode ser uma coisa muito difícil de aprender se você não tiver a experiência e foi apresentado ao IO através dos fluxos java.io.

Josh
fonte
2
Obrigado, informações úteis. Eu ainda argumentaria por algo como o Apache Commons, especialmente se ele usar nio (corretamente) por baixo; mas concordo que é importante entender os fundamentos subjacentes.
Peter
1
Infelizmente, existem advertências. Quando copiei o arquivo de 1,5 Gb no Windows 7, 32 bits, ele falhou ao mapear o arquivo. Eu tive que procurar outra solução.
Anton K.
15
Três possíveis problemas com o código acima: (a) se getChannel lança uma exceção, você pode vazar um fluxo aberto; (b) para arquivos grandes, você pode estar tentando transferir mais de uma vez do que o sistema operacional pode suportar; (c) você está ignorando o valor retornado por transferFrom; portanto, ele 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 é OK, breaks transferTo no JDK 1.4 no Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick
7
Acredito que esta versão atualizada atenda a essas preocupações: gist.github.com/889747 #
Mark Renouf
11
Este código tem um grande problema. transferTo () deve ser chamado em um loop. Não garante a transferência de todo o valor solicitado.
Marquês de Lorne
180

Agora, com o Java 7, você pode usar a seguinte sintaxe de tentativa com recurso:

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 realizado usando a nova classe Files introduzida no Java 7:

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

Bem esquisito, não é?

Scott
fonte
15
É incrível que o Java não tenha adicionado coisas assim antes de hoje. Certas operações são apenas o essencial absoluto para escrever software de computador. Os desenvolvedores de Java da Oracle poderiam aprender uma coisa ou duas dos sistemas operacionais, observando quais serviços eles fornecem, para facilitar a migração dos novatos.
Rick Hodgin
2
Ah obrigado! Eu não estava ciente da nova classe "Arquivos" com todas as suas funções auxiliares. Tem exatamente o que eu preciso. Obrigado pelo exemplo.
ChrisCantrell
1
desempenho, java O NIO FileChannel é melhor, leia este artigo journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj
5
Este código tem um grande problema. transferTo () deve ser chamado em um loop. Não garante a transferência de todo o valor solicitado.
Marquês de Lorne
@ Scott: Pete pediu uma solução em uma linha e você está tão perto ... é desnecessário agrupar Files.copy em um método copyFile. Eu apenas colocaria o Files.copy (Path from, Path to) no início da sua resposta e mencionaria que você pode usar File.toPath () se você tiver objetos de arquivo existentes: Files.copy (fromFile.toPath (), toFile.toPath ())
rob
89
  • Esses métodos são projetados para desempenho (eles se integram à E / S nativa do sistema operacional).
  • Esses métodos funcionam com arquivos, diretórios e links.
  • Cada uma das opções fornecidas pode ficar de fora - elas são opcionais.

A classe 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<FileVisitOption> 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<Path>  {
        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("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").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("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").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("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES
                 java.nio.file.LinkOption.NOFOLLOW_LINKS );
Glen Best
fonte
O nome do pacote para Arquivos estava errado (deve ser java.nio.file, não java.nio). Enviei uma edição para isso; espero que esteja tudo bem!
Stuart Rossiter
43

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);
Kevin Sadler
fonte
1
O que sua resposta adiciona à de Scott ou Glen?
Uri Agassi
11
É conciso, menos é mais. Suas respostas são boas e detalhadas, mas senti falta delas ao examiná-las. Infelizmente, existem muitas respostas para isso e muitas são longas, obsoletas e complicadas, e as boas respostas de Scott e Glen se perderam nisso (darei votos positivos para ajudar nisso). Gostaria de saber se minha resposta pode ser melhorada, reduzindo-a para três linhas, eliminando a mensagem de erro existe () e.
Kevin Sadler
Isso não funciona para diretórios. Droga, todo mundo está entendendo errado. Uma comunicação mais API causa sua falha. Eu também entendi errado.
mmm
2
@ momo a questão era como copiar um arquivo.
Kevin Sadler
28

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();
    }
}
Rakshi
fonte
1
Isso vai funcionar, mas não acho que seja melhor do que as outras respostas aqui?
Rup
2
@Rup É consideravelmente melhor do que as outras respostas aqui, (a) porque funciona e (b) porque não depende de software de terceiros.
Marquês de Lorne
1
@EJP OK, mas não é muito inteligente. A cópia de arquivo deve ser uma operação do sistema operacional ou do sistema de arquivos, não uma operação de aplicativo: esperamos que o Java consiga localizar uma cópia e transformá-la em uma operação do sistema operacional, exceto lendo explicitamente o arquivo em que você está impedindo isso. Se você não acha que o Java pode fazer isso, confiaria na otimização de 1K de leituras e gravações em blocos maiores? E se a origem e o destino estiverem em um compartilhamento remoto em uma rede lenta, isso está claramente fazendo um trabalho desnecessário. Sim, alguns JARs de terceiros são estupidamente grandes (Goiaba!), Mas eles adicionam muitas coisas como essas feitas corretamente.
Rup
Funcionou como um encanto. A melhor solução que não requer bibliotecas de terceiros e funciona em java 1.6. Obrigado.
James Wierzba
@Rup Concordo que deve ser uma função do sistema operacional, mas não consigo entender o seu comentário. A parte após o primeiro cólon está sem um verbo em algum lugar; Eu não 'confiava' em não esperar que o Java transforme blocos de 1k em algo maior, embora eu certamente usasse blocos muito maiores; Eu nunca escreveria um aplicativo que usasse arquivos compartilhados em primeiro lugar; e não estou ciente de que qualquer biblioteca de terceiros faça algo mais 'adequado' (o que você quer dizer com isso) do que esse código, exceto provavelmente para usar um buffer maior.
Marquês de Lorne
24

Observe que todos esses mecanismos copiam apenas o conteúdo do arquivo, não os metadados, como permissões. Portanto, se você copiar ou mover um arquivo .sh executável no linux, o novo arquivo não será 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!

Brad na Kademi
fonte
23

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

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

Aviso: se torepresenta um arquivo existente, esse arquivo será substituído pelo conteúdo de from. Se toe se fromreferir ao mesmo arquivo, o conteúdo desse arquivo será excluído.

Parâmetros:from - o arquivo de origem to- o arquivo de destino

Lança: IOException - se ocorrer um erro de E / S IllegalArgumentException- sefrom.equals(to)

Andrew McKinlay
fonte
7

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

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

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

saji
fonte
7

Se você estiver em um aplicativo Web que já use o Spring e se não desejar incluir o Apache Commons IO para cópia simples de arquivos, poderá usar o FileCopyUtils da estrutura do Spring.

Balaji Paulrajan
fonte
7

Aqui estão três maneiras de copiar facilmente 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 :

Arquivos # copy

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Jaskey
fonte
O primeiro não funciona para diretórios. Droga, todo mundo está entendendo errado. Uma comunicação mais API causa sua falha. Eu também entendi errado.
mmm
Primeiro é necessário 3 parâmetros. Files.copyutilizando apenas dois parâmetros é para Patha Stream. Basta adicionar o parâmetro StandardCopyOption.COPY_ATTRIBUTESou StandardCopyOption.REPLACE_EXISTINGpara PathaPath
Pimp Trizkit
6
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();
        }
    }
}
user3200607
fonte
Portanto, a diferença da resposta mais aceita é que você tem o transferFrom em um loop while?
Rup
1
Nem compila, e a chamada createNewFile () é redundante e desperdiça.
Marquês de Lorne
3

A cópia NIO com um buffer é a mais rápida de acordo com o meu teste. Veja o código de funcionamento 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();
        }    
    }
}

}

Tony
fonte
agradável! este é rápido, em vez de fluxo standar java.io .. copiar 10GB apenas em 160 segundos
aswzen
2

Rápido e trabalhe com todas as versões do Java e também do 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();
}
user1079877
fonte
1
Porém, nem todos os sistemas de arquivos suportam arquivos mapeados na memória e acho que é relativamente caro para arquivos pequenos.
Rup
Não funciona com nenhuma versão do Java anterior à 1.4 e não há nada que garanta que uma única gravação seja suficiente.
Marquês de Lorne
1

Um pouco atrasado para a festa, mas aqui está uma comparação do tempo necessário para copiar um arquivo usando vários métodos de cópia de arquivo. Eu segui os métodos por 10 vezes e fiz uma média. A transferência de arquivos usando fluxos de E / S parece ser o pior candidato:

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

Aqui estão os métodos:

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 vejo ao usar a classe de canal NIO é que ainda não consigo encontrar uma maneira de mostrar o progresso intermediário da cópia de arquivo.

Vinit Shandilya
fonte