Domanda

Mi ha sempre infastidito il fatto che l'unico modo per copiare un file in Java riguardi l'apertura di flussi, la dichiarazione di un buffer, la lettura di un file, il suo ciclo continuo e la sua scrittura sull'altro steam. Il Web è disseminato di implementazioni simili, ma ancora leggermente diverse di questo tipo di soluzione.

Esiste un modo migliore che rientra nei limiti del linguaggio Java (il che significa che non implica l'esecuzione di comandi specifici del sistema operativo)? Forse in qualche affidabile pacchetto di utilità open source, questo almeno oscurerebbe questa implementazione sottostante e fornirebbe una soluzione a una riga?

È stato utile?

Soluzione

Come menzionato in precedenza il toolkit, Apache Commons IO è la strada da percorrere, nello specifico FileUtils . copyFile () ; gestisce tutto il sollevamento pesante per te.

E come poscritto, nota che le recenti versioni di FileUtils (come la versione 2.0.1) hanno aggiunto l'uso di NIO per la copia dei file; NIO può aumentare significativamente le prestazioni di copia dei file , in gran parte perché le routine NIO rimanda la copia direttamente al sistema operativo / file system anziché gestirlo leggendo e scrivendo byte attraverso il livello Java. Quindi, se stai cercando prestazioni, potrebbe valere la pena verificare che stai utilizzando una versione recente di FileUtils.

Altri suggerimenti

Eviterei l'uso di un mega api come i comuni di Apache. Questa è un'operazione semplicistica ed è integrata nel JDK nel nuovo pacchetto NIO. In un certo senso era già stato collegato in una risposta precedente, ma il metodo chiave nell'API NIO sono le nuove funzioni "transferTo" e " transferFrom " ;.

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

Uno degli articoli collegati mostra un ottimo modo per integrare questa funzione nel tuo codice, 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();
        }
    }
}

Imparare NIO può essere un po 'complicato, quindi potresti voler fidarti di questo meccanico prima di partire e provare a imparare NIO dall'oggi al domani. Dall'esperienza personale può essere molto difficile imparare se non si dispone dell'esperienza e sono stati introdotti all'IO tramite i flussi java.io.

Ora con Java 7, è possibile utilizzare la seguente sintassi di prova con risorsa:

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, meglio ancora, questo può essere realizzato anche usando la nuova classe Files introdotta in Java 7:

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

Abbastanza elegante, eh?

  • Questi metodi sono progettati per le prestazioni (si integrano con l'I / O nativo del sistema operativo).
  • Questi metodi funzionano con file, directory e collegamenti.
  • Ciascuna delle opzioni fornite può essere esclusa - sono opzionali.

La classe di utilità

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

Copia di una directory o di un file

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

Spostamento di una directory o di un file

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

Copia ricorsiva di una directory o di un file

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

In Java 7 è facile ...

File src = new File("original.txt");
File target = new File("copy.txt");

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

Per copiare un file e salvarlo nel percorso di destinazione è possibile utilizzare il metodo seguente.

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

Nota che tutti questi meccanismi copiano solo il contenuto del file, non i metadati come le autorizzazioni. Quindi, se dovessi copiare o spostare un file .sh eseguibile su Linux, il nuovo file non sarebbe eseguibile.

Per poter veramente copiare o spostare un file, cioè per ottenere lo stesso risultato della copia da una riga di comando, devi effettivamente usare uno strumento nativo. O uno script shell o JNI.

Apparentemente, questo potrebbe essere risolto in java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Incrocio le dita!

La libreria Guava di Google ha anche un metodo di copia :

public static void copy(File from,
                        File to)
                 throws IOException
Copia tutti i byte da un file all'altro.

Avviso: se in rappresenta un file esistente, quel file  verrà sovrascritto con il contenuto di da . Se in e   da si riferisce allo stesso file, il contenuto di quel file  sarà cancellato.

Parameters: da - il file sorgente in - il file di destinazione

Produce: IOException - se si verifica un errore I / O IllegalArgumentException - se from.equals(to)

Disponibile come standard in Java 7, path.copy http://openjdk.java.net/projects/nio /javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/tutorial /essential/io/copy.html

Non riesco a credere che ci sia voluto tanto tempo per standardizzare qualcosa di così comune e semplice come la copia di file :(

Tre possibili problemi con il codice sopra:

  1. Se getChannel genera un'eccezione, potresti perdere un flusso aperto.
  2. Per file di grandi dimensioni, potresti provare a trasferire più dati contemporaneamente di quanto il sistema operativo sia in grado di gestire.
  3. Stai ignorando il valore restituito da transferFrom, quindi potrebbe essere la copia solo di una parte del file.

Ecco perché org.apache.tools.ant.util.ResourceUtils.copyResource è così complicato. Si noti inoltre che mentre transferFrom è OK, transferTo si interrompe su JDK 1.4 su Linux (consultare ID bug: 5056395 ) & # 8211; Jesse Glick Jan

Se si utilizza un'applicazione Web che utilizza già Spring e se non si desidera includere Apache Commons IO per la semplice copia di file, è possibile utilizzare FileCopyUtils del framework Spring.

Ecco tre modi in cui puoi facilmente copiare i file con una singola riga di codice!

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

Guava :

File # copia

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 NIO con un buffer è la più veloce secondo il mio test. Vedi il codice di lavoro di seguito da un mio progetto di prova su 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();
        }    
    }
}

}

Veloce e funziona con tutte le versioni di Java anche 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 po 'tardi per la festa, ma ecco un confronto del tempo impiegato per copiare un file usando vari metodi di copia dei file. Ho passato in rassegna i metodi per 10 volte e ho preso una media. Il trasferimento di file tramite flussi IO sembra essere il peggior candidato:

 Confronto del trasferimento di file usando vari metodi

Ecco i metodi:

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

L'unico inconveniente che posso vedere durante l'utilizzo della classe di canali NIO è che non riesco ancora a trovare un modo per mostrare l'avanzamento della copia dei file intermedi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top