Modo conciso standard per copiare un file in Java?


421

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 rientri nei limiti del linguaggio Java (il significato 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?


5
Potrebbe esserci qualcosa in Apache Commons FileUtils , in particolare i metodi copyFile .
toolkit,

22
Se si utilizza Java 7, utilizzare Files.copy invece, come raccomandato dal @GlenBest: stackoverflow.com/a/16600787/44737
Rob

Risposte:


274

Come menzionato in precedenza nel toolkit, Apache Commons IO è la strada da percorrere, in particolare 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 rimandano 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.


1
Molto utile - hai qualche idea su quando una versione ufficiale includerà questi nio cambiamenti?
Peter,

2
Rilascio pubblico di Apache Commons IO ancora all'1.4, grrrrrrr
Peter

14
A partire da dicembre 2010, Apache Commons IO è al 2.0.1, che ha la funzionalità NIO. Risposta aggiornata
Simon Nickerson,

4
Un avvertimento per le persone Android: questo NON è incluso nelle API Android standard
IlDan

18
Se si utilizza Java 7 o successiva, è possibile utilizzare Files.copy come suggerito da @GlenBest: stackoverflow.com/a/16600787/44737
Rob

278

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 qualche modo 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 durante la notte. Dall'esperienza personale può essere una cosa molto difficile da imparare se non si dispone dell'esperienza e sono stati introdotti all'IO tramite i flussi java.io.


2
Grazie, informazioni utili. Discuterei ancora per qualcosa come Apache Commons, specialmente se usa nio (correttamente) sotto; ma sono d'accordo che è importante capire i fondamenti di base.
Peter,

1
Sfortunatamente, ci sono avvertimenti. Quando ho copiato il file da 1,5 GB su Windows 7, a 32 bit, non è stato possibile mappare il file. Ho dovuto cercare un'altra soluzione.
Anton K.

15
Tre possibili problemi con il codice sopra: (a) se getChannel genera un'eccezione, si potrebbe perdere un flusso aperto; (b) per file di grandi dimensioni, è possibile che si stia tentando di trasferire più dati contemporaneamente di quanto il sistema operativo sia in grado di gestire; (c) stai ignorando il valore di ritorno di transferFrom, quindi potrebbe essere la copia solo di una parte del file. Ecco perché org.apache.tools.ant.util.ResourceUtils.copyResource è così complicato. Inoltre, mentre transferFrom è OK, transferTo si interrompe su JDK 1.4 su Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick,

7
Credo che questa versione aggiornata risolva
Mark Renouf,

11
Questo codice ha un grosso problema. transferTo () deve essere chiamato in un ciclo. Non garantisce il trasferimento dell'intero importo richiesto.
Marchese di Lorne,

180

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?


15
È incredibile che Java non abbia aggiunto cose come questa prima di oggi. Alcune operazioni sono solo gli elementi essenziali per la scrittura di software per computer. Gli sviluppatori Oracle di Java potrebbero imparare una o due cose dai sistemi operativi, osservando quali servizi forniscono, per rendere più facile la migrazione dei neofiti.
Rick Hodgin,

2
Ah grazie! Non ero a conoscenza della nuova classe "File" con tutte le sue funzioni di supporto. Ha esattamente quello di cui ho bisogno. Grazie per l'esempio
ChrisCantrell,

1
per quanto riguarda le prestazioni, java NIO FileChannel è meglio, leggi questo articolo journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj

5
Questo codice ha un grosso problema. transferTo () deve essere chiamato in un ciclo. Non garantisce il trasferimento dell'intero importo richiesto.
Marchese di Lorne,

@Scott: Pete ha chiesto una soluzione a una riga e sei così vicino ... non è necessario avvolgere Files.copy in un metodo copyFile. Metterei Files.copy (Path from, Path to) all'inizio della tua risposta e menzionerei che puoi usare File.toPath () se hai oggetti File esistenti: Files.copy (fromFile.toPath (), toFile.toPath ())
rob

89
  • 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.
  • Ognuna 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 );

Il nome del pacchetto per File era errato (dovrebbe essere java.nio.file non java.nio). Ho inviato una modifica per quello; spero che vada bene!
Stuart Rossiter,

43

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

1
Cosa aggiunge la tua risposta a Scott o Glen?
Uri Agassi,

11
È conciso, meno è di più. Le loro risposte sono buone e dettagliate, ma mi sono mancate guardando attraverso. Sfortunatamente ci sono molte risposte a questo e molte di loro sono lunghe, obsolete e complicate e le buone risposte di Scott e Glen si sono perse in questo (darò voti utili per aiutarlo). Mi chiedo se la mia risposta potrebbe essere migliorata riducendola a tre righe eliminando il esiste () e il messaggio di errore.
Kevin Sadler,

Questo non funziona per le directory. Accidenti a tutti sta sbagliando questo. Più di una comunicazione API causa l'errore. Anch'io ho sbagliato.
mmm,

2
@momo la domanda era come copiare un file.
Kevin Sadler,

28

Per copiare un file e salvarlo sul 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();
    }
}

1
Funzionerà, ma non credo sia meglio delle altre risposte qui?
Rup,

2
@Rup È notevolmente migliore delle altre risposte qui, (a) perché funziona e (b) perché non si basa su software di terze parti.
Marchese di Lorne,

1
@EJP OK, ma non è molto intelligente. La copia dei file dovrebbe essere un'operazione del sistema operativo o del file system, non un'operazione dell'applicazione: si spera che Java possa individuare una copia e trasformarla in un'operazione del sistema operativo, tranne leggendo esplicitamente il file in cui lo stai interrompendo. Se non pensi che Java possa farlo, ti fideresti di ottimizzare letture e scritture 1K in blocchi più grandi? E se l'origine e la destinazione fossero su una condivisione remota su una rete lenta, questo sta chiaramente facendo un lavoro non necessario. Sì, alcuni JAR di terze parti sono stupidamente grandi (Guava!) Ma aggiungono molte cose come questa fatte correttamente.
Rup,

Ha funzionato come un fascino. La migliore soluzione che non richiede librerie di terze parti e funziona su Java 1.6. Grazie.
James Wierzba,

@Rup Sono d'accordo che dovrebbe essere una funzione del sistema operativo, ma non riesco a dare alcun senso al tuo commento. La parte dopo i primi due punti manca da qualche parte un verbo; Non mi sarei "fidato" di non aspettarmi che Java trasformasse blocchi da 1k in qualcosa di più grande, anche se certamente avrei usato blocchi molto più grandi da solo; Non scriverei mai un'applicazione che utilizzava file condivisi in primo luogo; e non sono a conoscenza del fatto che qualsiasi libreria di terze parti fa qualcosa di più "corretto" (qualunque cosa tu intenda con questo) di questo codice, tranne probabilmente per usare un buffer più grande.
Marchese di Lorne,

24

Si noti 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, ovvero ottenere lo stesso risultato della copia da una riga di comando, è necessario utilizzare 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 . Dita incrociate!


23

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

copia vuota statica pubblica ( File  da,
                         File  a)
                 genera IOException
Copia tutti i byte da un file a un altro.

Avviso: se torappresenta un file esistente, quel file verrà sovrascritto con il contenuto di from. Se toe si fromriferiscono allo stesso file, il contenuto di quel file verrà eliminato.

Parametri:from - il file di origine to- il file di destinazione

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



7

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ù di una volta 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.

Questo è il motivo org.apache.tools.ant.util.ResourceUtils.copyResourceper cui è così complicato. Si noti inoltre che mentre transferFrom è OK, transferTo si interrompe su JDK 1.4 su Linux (vedere Bug ID: 5056395 ) - Jesse Glick Jan


7

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.


7

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

Java7 :

java.nio.file.Files # copia

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

Il primo non funziona per le directory. Accidenti a tutti sta sbagliando questo. Più di una comunicazione API causa l'errore. Anch'io ho sbagliato.
mmm

Il primo richiede 3 parametri. Files.copyutilizzando solo 2 parametri è per Pathdi Stream. Basta aggiungere il parametro StandardCopyOption.COPY_ATTRIBUTESo StandardCopyOption.REPLACE_EXISTINGfor PathtoPath
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();
        }
    }
}

Quindi la differenza dalla risposta più accettata è che hai il trasferimento Da un ciclo while?
Rup,

1
Non si compila nemmeno e la chiamata createNewFile () è ridondante e dispendiosa.
Marchese di Lorne,

3

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

}


simpatico! questo è veloce piuttosto che standar java.io stream .. copiando 10 GB solo in 160 secondi
circa

2

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

1
Tuttavia, non tutti i filesystem supportano file mappati in memoria e penso che sia relativamente costoso per file di piccole dimensioni.
Rup,

Non funziona con nessuna versione di Java precedente alla 1.4 e non c'è nulla che garantisca che sia sufficiente una sola scrittura.
Marchese di Lorne,

1

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 con 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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.