Manière concise standard de copier un fichier en Java?

421

Cela m'a toujours dérangé que la seule façon de copier un fichier en Java consiste à ouvrir des flux, à déclarer un tampon, à lire un fichier, à le parcourir et à l'écrire sur l'autre vapeur. Le Web est jonché d'implémentations similaires, mais encore légèrement différentes de ce type de solution.

Existe-t-il un meilleur moyen de rester dans les limites du langage Java (ce qui ne signifie pas d'exécuter des commandes spécifiques au système d'exploitation)? Peut-être que dans un paquet utilitaire open source fiable, cela masquerait au moins cette implémentation sous-jacente et fournirait une solution en une seule ligne?

Peter
la source
5
Il pourrait y avoir quelque chose dans Apache Commons FileUtils , en particulier, les méthodes copyFile .
toolkit
22
Si vous utilisez Java 7, utilisez Files.copy à la place, tel que recommandé par @GlenBest: stackoverflow.com/a/16600787/44737
rob

Réponses:

274

Comme le mentionne la boîte à outils ci-dessus, Apache Commons IO est la voie à suivre, en particulier FileUtils . copyFile () ; il gère pour vous tous les travaux lourds.

Et en tant que post-scriptum, notez que les versions récentes de FileUtils (telles que la version 2.0.1) ont ajouté l'utilisation de NIO pour copier des fichiers; NIO peut augmenter considérablement les performances de copie de fichiers , en grande partie parce que les routines NIO reportent la copie directement sur le système d'exploitation / fichier plutôt que de la gérer en lisant et en écrivant des octets via la couche Java. Donc, si vous recherchez des performances, il peut être utile de vérifier que vous utilisez une version récente de FileUtils.

delfuego
la source
1
Très utile - avez-vous une idée de la date à laquelle une version officielle intégrera ces modifications nio?
Peter
2
Sortie publique d'Apache Commons IO toujours à 1.4, grrrrrrr
Peter
14
En décembre 2010, Apache Commons IO est à 2.0.1, qui a la fonctionnalité NIO. Réponse mise à jour.
Simon Nickerson
4
Un avertissement pour les personnes Android: cela n'est PAS inclus dans les API Android standard
IlDan
18
Si vous utilisez Java 7 ou plus récent, vous pouvez utiliser Files.copy comme suggéré par @GlenBest: stackoverflow.com/a/16600787/44737
rob
278

J'éviterais l'utilisation d'un méga api comme apache commons. Il s'agit d'une opération simpliste et intégrée au JDK dans le nouveau package NIO. C'était déjà un peu lié dans une réponse précédente, mais la méthode clé dans l'API NIO sont les nouvelles fonctions "transferTo" et "transferFrom".

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

L'un des articles liés montre un excellent moyen d'intégrer cette fonction dans votre code, en utilisant le 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();
        }
    }
}

Apprendre NIO peut être un peu délicat, donc vous voudrez peut-être simplement faire confiance à ce mécanicien avant de partir et d'essayer d'apprendre NIO du jour au lendemain. Par expérience personnelle, il peut être très difficile d'apprendre si vous n'avez pas l'expérience et que vous avez été initié à IO via les flux java.io.

Josh
la source
2
Merci, infos utiles. Je plaiderais toujours pour quelque chose comme Apache Commons, surtout s'il utilise nio (correctement) en dessous; mais je suis d'accord qu'il est important de comprendre les fondamentaux sous-jacents.
Peter
1
Malheureusement, il y a des mises en garde. Lorsque j'ai copié un fichier de 1,5 Go sur Windows 7, 32 bits, il n'a pas réussi à mapper le fichier. J'ai dû chercher une autre solution.
Anton K.
15
Trois problèmes possibles avec le code ci-dessus: (a) si getChannel lève une exception, vous risquez de divulguer un flux ouvert; (b) pour les fichiers volumineux, vous essayez peut-être de transférer plus d'un coup que le système d'exploitation ne peut en gérer; (c) vous ignorez la valeur de retour de transferFrom, il peut donc ne copier qu'une partie du fichier. C'est pourquoi org.apache.tools.ant.util.ResourceUtils.copyResource est si compliqué. Notez également que tandis que transferFrom est OK, transferTo se casse sur JDK 1.4 sous Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick
7
Je crois que cette version mise à jour répond à ces préoccupations: gist.github.com/889747
Mark Renouf
11
Ce code a un problème majeur . transferTo () doit être appelé en boucle. Il ne garantit pas de transférer la totalité du montant demandé.
Marquis de Lorne du
180

Désormais, avec Java 7, vous pouvez utiliser la syntaxe try-with-resource suivante:

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, mieux encore, cela peut également être accompli en utilisant la nouvelle classe Files introduite dans Java 7:

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

Assez chic, hein?

Scott
la source
15
C'est incroyable que Java n'ait pas ajouté des choses comme ça avant aujourd'hui. Certaines opérations ne sont que les éléments essentiels de l'écriture de logiciels informatiques. Les développeurs Oracle de Java pourraient apprendre une ou deux choses des systèmes d'exploitation, en regardant quels services ils fournissent, pour faciliter la migration des débutants.
Rick Hodgin
2
Ah merci! Je n'étais pas au courant de la nouvelle classe "Files" avec toutes ses fonctions d'assistance. Il a exactement ce dont j'ai besoin. Merci pour l'exemple.
ChrisCantrell
1
en termes de performances, java NIO FileChannel est meilleur, lisez cet article journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj
5
Ce code a un problème majeur . transferTo () doit être appelé en boucle. Il ne garantit pas de transférer la totalité du montant demandé.
Marquis de Lorne du
@Scott: Pete a demandé une solution sur une ligne et vous êtes si proche ... il n'est pas nécessaire d'envelopper Files.copy dans une méthode copyFile. Je mettrais simplement Files.copy (Path from, Path to) au début de votre réponse et mentionner que vous pouvez utiliser File.toPath () si vous avez des objets File existants: Files.copy (fromFile.toPath (), toFile.toPath ())
rob
89
  • Ces méthodes sont conçues pour les performances (elles s'intègrent aux E / S natives du système d'exploitation).
  • Ces méthodes fonctionnent avec des fichiers, des répertoires et des liens.
  • Chacune des options fournies peut être omise - elles sont facultatives.

La classe d'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;
    }
}

Copie d'un répertoire ou d'un fichier

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

Déplacer un répertoire ou un fichier

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

Copie récursive d'un répertoire ou d'un fichier

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
la source
Le nom du package pour Files était incorrect (devrait être java.nio.file et non java.nio). J'ai soumis une modification pour cela; j'espère que ça va!
Stuart Rossiter
43

En Java 7, c'est facile ...

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

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
Kevin Sadler
la source
1
Qu'est-ce que votre réponse ajoute à Scott's ou Glen's?
Uri Agassi
11
C'est concis, moins c'est plus. Leurs réponses sont bonnes et détaillées, mais je les ai manquées en les parcourant. Malheureusement, il y a beaucoup de réponses à cela et beaucoup d'entre elles sont longues, obsolètes et compliquées et les bonnes réponses de Scott et Glen s'y sont perdues (je donnerai des votes pour aider à cela). Je me demande si ma réponse pourrait être améliorée en la réduisant à trois lignes en supprimant le message exist () et d'erreur.
Kevin Sadler
Cela ne fonctionne pas pour les répertoires. Merde, tout le monde se trompe. Plus d'une communication API émet votre faute. Moi aussi, je me suis trompé.
mmm
2
@momo, la question était de savoir comment copier un fichier.
Kevin Sadler
28

Pour copier un fichier et l'enregistrer sur votre chemin de destination, vous pouvez utiliser la méthode ci-dessous.

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
la source
1
Cela fonctionnera, mais je ne pense pas que ce soit mieux que les autres réponses ici?
Rup
2
@Rup C'est beaucoup mieux que les autres réponses ici, (a) parce que cela fonctionne, et (b) parce qu'il ne dépend pas d'un logiciel tiers.
Marquis de Lorne
1
@EJP OK, mais ce n'est pas très intelligent. La copie de fichiers doit être une opération de système d'exploitation ou de système de fichiers, et non une opération d'application: Java peut, espérons-le, repérer une copie et la transformer en opération de système d'exploitation, sauf en lisant explicitement le fichier si vous ne le faites pas. Si vous ne pensez pas que Java peut le faire, lui feriez-vous confiance pour optimiser les lectures et les écritures 1K dans des blocs plus grands? Et si la source et la destination se trouvaient sur un partage distant sur un réseau lent, cela fait clairement un travail inutile. Oui, certains fichiers JAR tiers sont stupidement grands (Goyave!), Mais ils ajoutent beaucoup de choses comme ça, faites correctement.
Rup
A fonctionné comme un charme. Meilleure solution qui ne nécessite pas de bibliothèques tierces et fonctionne sur java 1.6. Merci.
James Wierzba
@Rup Je suis d'accord que ce devrait être une fonction du système d'exploitation, mais je ne peux donner aucun autre sens à votre commentaire. La partie après le premier deux-points manque quelque part de verbe; Je ne ferais pas "confiance" et je ne m'attendrais pas à ce que Java transforme des blocs 1k en quelque chose de plus grand, bien que j'utiliserais certainement des blocs beaucoup plus grands moi-même; Je n'écrirais jamais une application qui utilisait des fichiers partagés en premier lieu; et je ne suis pas au courant qu'aucune bibliothèque tierce ne fasse quelque chose de plus «approprié» (quoi que vous entendiez par là) que ce code, sauf probablement pour utiliser un tampon plus grand.
Marquis de Lorne
24

Notez que tous ces mécanismes copient uniquement le contenu du fichier, pas les métadonnées telles que les autorisations. Donc, si vous deviez copier ou déplacer un fichier .sh exécutable sur Linux, le nouveau fichier ne serait pas exécutable.

Pour vraiment copier ou déplacer un fichier, c'est-à-dire pour obtenir le même résultat que la copie à partir d'une ligne de commande, vous devez en fait utiliser un outil natif. Soit un script shell ou JNI.

Apparemment, cela pourrait être corrigé dans java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Doigts croisés!

Brad chez Kademi
la source
23

La bibliothèque Guava de Google a également une méthode de copie :

copie publique statique nulle ( fichier  de,
                         fichier  vers)
                 lève IOException
Copie tous les octets d'un fichier dans un autre.

Avertissement: si toreprésente un fichier existant, ce fichier sera remplacé par le contenu de from. Si toet fromfaites référence au même fichier, le contenu de ce fichier sera supprimé.

Paramètres:from - le fichier source to- le fichier de destination

Lance: IOException - si une erreur d'E / S se produit IllegalArgumentException- sifrom.equals(to)

Andrew McKinlay
la source
7

Trois problèmes possibles avec le code ci-dessus:

  1. Si getChannel lève une exception, vous risquez de divulguer un flux ouvert.
  2. Pour les fichiers volumineux, vous essayez peut-être de transférer plus d'un coup que le système d'exploitation ne peut en gérer.
  3. Vous ignorez la valeur de retour de transferFrom, il peut donc ne copier qu'une partie du fichier.

C'est pourquoi org.apache.tools.ant.util.ResourceUtils.copyResourcec'est si compliqué. Notez également que tandis que transferFrom est OK, transferTo se casse sur JDK 1.4 sous Linux (voir ID de bogue: 5056395 ) - Jesse Glick Jan

saji
la source
7

Si vous êtes dans une application Web qui utilise déjà Spring et si vous ne souhaitez pas inclure Apache Commons IO pour la simple copie de fichiers, vous pouvez utiliser FileCopyUtils du framework Spring.

Balaji Paulrajan
la source
7

Voici trois façons de copier facilement des fichiers avec une seule ligne de code!

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

Goyave :

Fichiers # copie

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Jaskey
la source
Le premier ne fonctionne pas pour les répertoires. Merde, tout le monde se trompe. Plus d'une communication API émet votre faute. Moi aussi, je me suis trompé.
mmm
Le premier a besoin de 3 paramètres. Files.copyest à l' aide de seulement deux paramètres Pathà Stream. Il suffit d' ajouter le paramètre StandardCopyOption.COPY_ATTRIBUTESou StandardCopyOption.REPLACE_EXISTINGpour PathàPath
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
la source
Donc, la différence par rapport à la meilleure réponse acceptée est que vous avez le transferFrom dans une boucle while?
Rup
1
Ne compile même pas, et l'appel createNewFile () est redondant et inutile.
Marquis de Lorne
3

La copie NIO avec un tampon est la plus rapide selon mon test. Voir le code de travail ci-dessous d'un de mes projets de test à 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
la source
agréable! celui-ci est rapide plutôt que le flux java.io standard .. copie de 10 Go uniquement en 160 secondes
aswzen
2

Rapide et fonctionne avec toutes les versions de Java également 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
la source
1
Cependant, tous les systèmes de fichiers ne prennent pas en charge les fichiers mappés en mémoire, et je pense que c'est relativement cher pour les petits fichiers.
Rup
Ne fonctionne avec aucune version de Java antérieure à 1.4, et rien ne garantit qu'une seule écriture est suffisante.
Marquis de Lorne
1

Un peu tard pour la fête, mais voici une comparaison du temps nécessaire pour copier un fichier en utilisant différentes méthodes de copie de fichier. J'ai parcouru les méthodes 10 fois et j'ai pris une moyenne. Le transfert de fichiers à l'aide de flux d'E / S semble être le pire candidat:

Comparaison du transfert de fichiers à l'aide de différentes méthodes

Voici les méthodes:

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

Le seul inconvénient que je peux voir lors de l'utilisation de la classe de canal NIO est que je n'arrive toujours pas à trouver un moyen d'afficher la progression de la copie de fichiers intermédiaires.

Vinit Shandilya
la source