¿Forma concisa estándar para copiar un archivo en Java?

421

Siempre me ha molestado que la única forma de copiar un archivo en Java sea abrir secuencias, declarar un búfer, leer un archivo, recorrerlo y escribirlo en el otro vapor. La web está llena de implementaciones similares, aunque todavía ligeramente diferentes, de este tipo de solución.

¿Existe una mejor manera de mantenerse dentro de los límites del lenguaje Java (el significado no implica ejecutar comandos específicos del sistema operativo)? ¿Quizás en algún paquete de utilidad de código abierto confiable, eso al menos oscurecería esta implementación subyacente y proporcionaría una solución de una línea?

Peter
fuente
55
Podría haber algo en Apache Commons FileUtils , específicamente, los métodos copyFile .
kit de herramientas
22
Si el uso de Java 7, utilice Files.copy lugar, según lo recomendado por @GlenBest: stackoverflow.com/a/16600787/44737
Rob

Respuestas:

274

Como el kit de herramientas menciona anteriormente, Apache Commons IO es el camino a seguir, específicamente FileUtils . copyFile () ; maneja todo el trabajo pesado por ti.

Y como posdata, tenga en cuenta que las versiones recientes de FileUtils (como la versión 2.0.1) han agregado el uso de NIO para copiar archivos; NIO puede aumentar significativamente el rendimiento de copia de archivos , en gran parte porque las rutinas de NIO difieren la copia directamente en el sistema operativo / sistema de archivos en lugar de manejarlo leyendo y escribiendo bytes a través de la capa de Java. Entonces, si está buscando rendimiento, puede valer la pena verificar que está utilizando una versión reciente de FileUtils.

delfuego
fuente
1
Muy útil: ¿tiene alguna idea de cuándo una versión oficial incorporará estos cambios de nio?
Peter
2
Lanzamiento público de Apache Commons IO todavía en 1.4, grrrrrrr
Peter
14
A partir de diciembre de 2010, Apache Commons IO está en 2.0.1, que tiene la funcionalidad NIO. Respuesta actualizada
Simon Nickerson
44
Una advertencia para las personas con Android: esto NO está incluido en las API estándar de Android
IlDan
18
Si el uso de Java 7 o posterior, puede utilizar Files.copy según lo sugerido por @GlenBest: stackoverflow.com/a/16600787/44737
Rob
278

Evitaría el uso de una mega api como apache commons. Esta es una operación simplista y está integrada en el JDK en el nuevo paquete NIO. Ya estaba vinculado en una respuesta anterior, pero el método clave en la API de NIO son las nuevas funciones "transferTo" y "transferFrom".

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

Uno de los artículos vinculados muestra una excelente manera de cómo integrar esta función en su código, utilizando 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 puede ser un poco complicado, por lo que es posible que desee confiar en esta mecánica antes de comenzar y tratar de aprender NIO de la noche a la mañana. Por experiencia personal puede ser muy difícil de aprender si no tienes la experiencia y te presentaron a IO a través de las transmisiones de java.io.

Josh
fuente
2
Gracias, información útil. Todavía argumentaría por algo como Apache Commons, especialmente si usa nio (correctamente) debajo; pero estoy de acuerdo en que es importante comprender los fundamentos subyacentes.
Peter
1
Lamentablemente, hay advertencias. Cuando copié el archivo de 1.5 Gb en Windows 7, 32 bits, no se pudo asignar el archivo. Tuve que buscar otra solución.
Anton K.
15
Tres posibles problemas con el código anterior: (a) si getChannel produce una excepción, puede filtrar una secuencia abierta; (b) para archivos grandes, es posible que esté intentando transferir más de una vez de lo que el sistema operativo puede manejar; (c) está ignorando el valor de retorno de transferFrom, por lo que podría estar copiando solo una parte del archivo. Es por eso que org.apache.tools.ant.util.ResourceUtils.copyResource es tan complicado. También tenga en cuenta que si bien transferFrom está bien, transferTo se rompe en JDK 1.4 en Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick
77
Creo que esta versión actualizada aborda esas preocupaciones: gist.github.com/889747
Mark Renouf
11
Este código tiene un gran problema. transferTo () debe invocarse en un bucle. No garantiza transferir todo el monto solicitado.
Marqués de Lorne
180

Ahora con Java 7, puede usar la siguiente sintaxis de prueba con recursos:

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() );
    }
}

O, mejor aún, esto también se puede lograr utilizando la nueva clase de archivos introducida en Java 7:

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

Bastante elegante, ¿eh?

Scott
fuente
15
Es sorprendente que Java no haya agregado cosas como esta antes de hoy. Ciertas operaciones son los elementos esenciales absolutos de escribir software de computadora. Los desarrolladores de Oracle de Java podrían aprender una o dos cosas de los sistemas operativos, observando qué servicios brindan, para que sea más fácil para los novatos migrar.
Rick Hodgin
2
¡Ah gracias! No tenía conocimiento de la nueva clase "Archivos" con todas sus funciones auxiliares. Tiene exactamente lo que necesito. Gracias por el ejemplo
ChrisCantrell
1
En cuanto al rendimiento, Java NIO FileChannel es mejor, lea este artículo journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj
55
Este código tiene un gran problema. transferTo () debe invocarse en un bucle. No garantiza transferir todo el monto solicitado.
Marqués de Lorne
@Scott: Pete pidió una solución de una línea y estás tan cerca ... no es necesario envolver Files.copy en un método copyFile. Simplemente pondría Files.copy (Path from, Path to) al comienzo de su respuesta y mencionaría que puede usar File.toPath () si tiene objetos File existentes: Files.copy (fromFile.toPath (), toFile.toPath ())
robar el
89
  • Estos métodos están diseñados para el rendimiento (se integran con las E / S nativas del sistema operativo).
  • Estos métodos funcionan con archivos, directorios y enlaces.
  • Cada una de las opciones suministradas puede omitirse; son opcionales.

La clase de utilidad

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;
    }
}

Copiar un directorio o archivo

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);

Mover un directorio o archivo

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);

Copiar un directorio o archivo de forma recursiva

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
fuente
El nombre del paquete para Archivos era incorrecto (debería ser java.nio.file, no java.nio). He enviado una edición para eso; Espero que esté bien!
Stuart Rossiter
43

En Java 7 es 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
fuente
1
¿Qué agrega su respuesta a Scott's o Glen's?
Uri Agassi
11
Es conciso, menos es más. Sus respuestas son buenas y detalladas, pero las extrañé al mirar. Desafortunadamente, hay muchas respuestas a esto y muchas de ellas son largas, obsoletas y complicadas y las buenas respuestas de Scott y Glen se perdieron en eso (daré votos a favor para ayudar con eso). Me pregunto si mi respuesta podría mejorarse reduciéndola a tres líneas al eliminar el mensaje de error () y existe.
Kevin Sadler
Esto no funciona para directorios. Maldición, todos se están equivocando. Más de un problema de comunicación API es tu culpa. Yo también me equivoqué.
mmm
2
@momo la pregunta era cómo copiar un archivo.
Kevin Sadler
28

Para copiar un archivo y guardarlo en su ruta de destino, puede usar el siguiente método.

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
fuente
1
Esto funcionará, pero no creo que sea mejor que las otras respuestas aquí.
Rup
2
@Rup Es considerablemente mejor que las otras respuestas aquí, (a) porque funciona y (b) porque no depende de software de terceros.
Marqués de Lorne
1
@EJP OK, pero no es muy inteligente. La copia de archivos debe ser una operación del sistema operativo o del sistema de archivos, no una operación de la aplicación: Java, con suerte, puede detectar una copia y convertirla en una operación del sistema operativo, excepto al leer explícitamente el archivo en el que lo está deteniendo. Si no cree que Java pueda hacer eso, ¿confiaría en él para optimizar las lecturas y escrituras de 1K en bloques más grandes? Y si el origen y el destino estaban en un recurso compartido remoto a través de una red lenta, esto claramente está haciendo un trabajo innecesario. Sí, algunos JAR de terceros son estúpidamente grandes (¡Guava!), Pero agregan muchas cosas como esta hechas correctamente.
Rup
Trabajado como un encanto. La mejor solución que no requiere bibliotecas de terceros y funciona en Java 1.6. Gracias.
James Wierzba
@Rup Estoy de acuerdo en que debería ser una función del sistema operativo, pero no puedo entender tu comentario. La parte posterior al primer colon carece de un verbo en alguna parte; Ni 'confiar' no esperaría que Java convirtiera bloques de 1k en algo más grande, aunque ciertamente usaría bloques mucho más grandes yo mismo; Nunca escribiría una aplicación que usara archivos compartidos en primer lugar; y no estoy al tanto de que ninguna biblioteca de terceros haga algo más "apropiado" (lo que quiera decir con eso) que este código, excepto probablemente para usar un búfer más grande.
Marqués de Lorne
24

Tenga en cuenta que todos estos mecanismos solo copian el contenido del archivo, no los metadatos como los permisos. Entonces, si tuviera que copiar o mover un archivo .sh ejecutable en Linux, el nuevo archivo no sería ejecutable.

Para realmente copiar o mover un archivo, es decir, para obtener el mismo resultado que copiar desde una línea de comando, en realidad necesita usar una herramienta nativa. Ya sea un script de shell o JNI.

Aparentemente, esto podría solucionarse en Java 7: http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . ¡Dedos cruzados!

Brad en Kademi
fuente
23

La biblioteca de guayaba de Google también tiene un método de copia :

copia anulada estática pública ( Archivo  de,
                         Archivo  a)
                 lanza IOException
Copia todos los bytes de un archivo a otro.

Advertencia: si torepresenta un archivo existente, ese archivo se sobrescribirá con el contenido de from. Si toy hace fromreferencia al mismo archivo, se eliminará el contenido de ese archivo.

Parámetros:from - el archivo de origen to- el archivo de destino

Emite: IOException - si se produce un error de E / S IllegalArgumentException- sifrom.equals(to)

Andrew McKinlay
fuente
7

Tres posibles problemas con el código anterior:

  1. Si getChannel arroja una excepción, podría filtrar una transmisión abierta.
  2. Para archivos grandes, es posible que esté intentando transferir más de una vez de lo que el sistema operativo puede manejar.
  3. Está ignorando el valor de retorno de transferFrom, por lo que podría estar copiando solo parte del archivo.

Por eso org.apache.tools.ant.util.ResourceUtils.copyResourcees tan complicado. También tenga en cuenta que si bien transferFrom está bien, transferTo se rompe en JDK 1.4 en Linux (vea el ID de error: 5056395 ) - Jesse Glick Jan

saji
fuente
7

Si está en una aplicación web que ya usa Spring y no desea incluir Apache Commons IO para la copia simple de archivos, puede usar FileCopyUtils del marco Spring.

Balaji Paulrajan
fuente
7

¡Aquí hay tres formas en que puede copiar fácilmente archivos con una sola línea 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);
}

guayaba :

Archivos # copia

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Jaskey
fuente
El primero no funciona para directorios. Maldición, todos se están equivocando. Más de un problema de comunicación API es tu culpa. Yo también me equivoqué.
mmm
El primero necesita 3 parámetros. Files.copyusando sólo 2 parámetros es para Patha Stream. Sólo tiene que añadir el parámetro StandardCopyOption.COPY_ATTRIBUTESo 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();
        }
    }
}
usuario3200607
fuente
Entonces, ¿la diferencia con la respuesta más aceptada es que tienes transferFrom en un ciclo while?
Rup
1
Ni siquiera se compila, y la llamada createNewFile () es redundante y derrochadora.
Marqués de Lorne
3

La copia NIO con un buffer es la más rápida según mi prueba. Vea el código de trabajo a continuación de un proyecto de prueba mío en 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
fuente
¡bonito! este es rápido en lugar de la transmisión estándar de java.io ... copiando 10GB solo en 160 segundos
aswzen
2

Rápido y funciona con todas las versiones de Java también 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();
}
usuario1079877
fuente
1
Sin embargo, no todos los sistemas de archivos admiten archivos asignados en memoria, y creo que es relativamente costoso para archivos pequeños.
Rup
No funciona con ninguna versión de Java anterior a 1.4, y no hay nada que garantice que una sola escritura sea suficiente.
Marqués de Lorne
1

Un poco tarde para la fiesta, pero aquí hay una comparación del tiempo necesario para copiar un archivo utilizando varios métodos de copia de archivos. Entré en los métodos 10 veces y tomé un promedio. La transferencia de archivos usando flujos IO parece ser el peor candidato:

Comparación de transferencia de archivos usando varios métodos

Aquí están los 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);
}

El único inconveniente que puedo ver al usar la clase de canal NIO es que todavía parece que no puedo encontrar una manera de mostrar el progreso intermedio de la copia de archivos.

Vinit Shandilya
fuente