Pregunta

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

¿Existe una forma mejor que se mantenga dentro de los límites del lenguaje Java (es decir, que no implique ejecutar comandos específicos del sistema operativo)?¿Quizás en algún paquete de utilidad confiable de código abierto, eso al menos oscurecería esta implementación subyacente y proporcionaría una solución de una sola línea?

¿Fue útil?

Solución

Como el kit de herramientas menciona anteriormente, Apache Commons IO es el camino a seguir, específicamente Utilidades de archivo.copiar archivo();él se encarga de todo el trabajo pesado por usted.

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 NIO difieren la copia directamente al sistema operativo/sistema de archivos en lugar de manejarla leyendo y escribiendo bytes a través de la capa Java.Entonces, si buscas rendimiento, puede que valga la pena comprobar que estás utilizando una versión reciente de FileUtils.

Otros consejos

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 integrar esta función en su código, usando 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 desees confiar en esta mecánica antes de intentar aprender NIO de la noche a la mañana.Por experiencia personal, puede ser muy difícil aprender si no tienes la experiencia y conociste IO a través de las transmisiones de java.io.

Ahora, con Java 7, puede utilizar 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 usando la nueva clase Archivos introducida en Java 7:

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

Bastante elegante, ¿eh?

  • Estos métodos están diseñados para el rendimiento (se integran con la E/S nativa del sistema operativo).
  • Estos métodos funcionan con archivos, directorios y enlaces.
  • Cada una de las opciones proporcionadas 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 );

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

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

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

Tenga en cuenta que todos estos mecanismos solo copian el contenido del archivo, no los metadatos como los permisos.Entonces, si copiara o moviera 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 utilizar 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!

La biblioteca Guava de Google también tiene una método de copia:

public static void Copiar(Archivo from,
                        Archivo to)
                 throws IOExcepción
Copia todos los bytes de un archivo a otro.

Advertencia: Si to representa un archivo existente, ese archivo se sobrescribirá con el contenido de from.Si to y from referirse a mismo Archivo, se eliminará el contenido de ese archivo.

Parámetros:from - el archivo fuenteto - el archivo de destino

Lanza: IOExcepción - si ocurre un error de E/SArgumento de excepción ilegal - si from.equals(to)

Disponible como estándar en 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

No puedo creer que les haya tomado tanto tiempo estandarizar algo tan común y simple como copiar archivos :(

Tres posibles problemas con el código anterior:

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

Esta es la razón por org.apache.tools.ant.util.ResourceUtils.copyResource Es tan complicado.También tenga en cuenta que, si bien transferFrom está bien, transferTo se interrumpe en JDK 1.4 en Linux (consulte ID de error: 5056395) – Jesse Glick Jan

Si está en una aplicación web que ya usa Spring y no desea incluir Apache Commons IO para copiar archivos simples, puede usar ArchivoCopyUtils del marco de primavera.

¡Aquí hay tres formas en las que puedes copiar archivos fácilmente con una sola línea de código!

java7:

java.nio.file.Archivos#copiar

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

Appache Commons IO:

FileUtils#copiarArchivo

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

Guayaba :

Archivos#copiar

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

La copia de NIO con un búfer 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();
        }    
    }
}

}

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

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.Revisé los métodos 10 veces y tomé un promedio.La transferencia de archivos mediante flujos IO parece ser el peor candidato:

Comparison of file transfer using various methods

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 no puedo encontrar una manera de mostrar el progreso de la copia del archivo intermedio.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top