Cara ringkas dan standar untuk menyalin file di Java?

421

Itu selalu mengganggu saya bahwa satu-satunya cara untuk menyalin file di Jawa melibatkan membuka stream, mendeklarasikan buffer, membaca dalam satu file, mengulanginya, dan menuliskannya ke steam lain. Web penuh dengan implementasi yang serupa, namun masih sedikit berbeda dari solusi jenis ini.

Apakah ada cara yang lebih baik yang tetap dalam batas-batas bahasa Jawa (artinya tidak melibatkan mengeksekusi perintah spesifik OS)? Mungkin dalam beberapa paket utilitas open source yang dapat diandalkan, yang setidaknya akan mengaburkan implementasi mendasar ini dan memberikan solusi satu baris?

Peter
sumber
5
Mungkin ada sesuatu di FileUtils Apache Commons , Secara khusus, metode copyFile .
toolkit
22
Jika menggunakan Java 7, gunakan Files.copy sebagai gantinya, seperti yang direkomendasikan oleh @GlenBest: stackoverflow.com/a/16600787/44737
rob

Jawaban:

274

Seperti yang disebutkan oleh toolkit di atas, Apache Commons IO adalah caranya, khususnya FileUtils . copyFile () ; itu menangani semua angkat berat untuk Anda.

Dan sebagai catatan tambahan, perhatikan bahwa versi terbaru FileUtils (seperti rilis 2.0.1) telah menambahkan penggunaan NIO untuk menyalin file; NIO dapat secara signifikan meningkatkan kinerja penyalinan file , sebagian besar karena rutinitas NIO menunda menyalin langsung ke OS / sistem file daripada menanganinya dengan membaca dan menulis byte melalui layer Java. Jadi, jika Anda mencari kinerja, mungkin ada baiknya Anda memeriksa apakah Anda menggunakan versi terbaru FileUtils.

delfuego
sumber
1
Sangat membantu - apakah Anda memiliki wawasan tentang kapan rilis resmi akan memasukkan perubahan nio ini?
Peter
2
Rilis publik dari Apache Commons IO masih di 1,4, grrrrrrr
Peter
14
Pada Desember 2010, Apache Commons IO berada di 2.0.1, yang memiliki fungsi NIO. Jawaban diperbarui.
Simon Nickerson
4
Peringatan untuk orang Android: ini TIDAK termasuk dalam API Android standar
IlDan
18
Jika menggunakan Java 7 atau lebih baru, Anda dapat menggunakan Files.copy seperti yang disarankan oleh @GlenBest: stackoverflow.com/a/16600787/44737
rob
278

Saya akan menghindari penggunaan mega api seperti apache commons. Ini adalah operasi sederhana dan dibangun ke dalam JDK dalam paket NIO baru. Itu semacam sudah ditautkan dalam jawaban sebelumnya, tetapi metode kunci dalam api NIO adalah fungsi baru "transferTo" dan "transferFrom".

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

Salah satu artikel tertaut menunjukkan cara yang hebat tentang bagaimana mengintegrasikan fungsi ini ke dalam kode Anda, menggunakan 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();
        }
    }
}

Mempelajari NIO bisa sedikit rumit, jadi Anda mungkin ingin mempercayai mekanik ini sebelum berangkat dan mencoba belajar NIO dalam semalam. Dari pengalaman pribadi itu bisa menjadi hal yang sangat sulit untuk dipelajari jika Anda tidak memiliki pengalaman dan diperkenalkan ke IO melalui aliran java.io.

Josh
sumber
2
Terima kasih, info yang bermanfaat. Saya masih akan berdebat untuk sesuatu seperti Apache Commons, terutama jika menggunakan nio (dengan benar) di bawahnya; tetapi saya setuju bahwa penting untuk memahami dasar-dasarnya yang mendasarinya.
Peter
1
Sayangnya, ada peringatan. Ketika saya menyalin file 1,5 Gb pada Windows 7, 32 bit, gagal memetakan file. Saya harus mencari solusi lain.
Anton K.
15
Tiga kemungkinan masalah dengan kode di atas: (a) jika getChannel melempar pengecualian, Anda mungkin membocorkan aliran terbuka; (b) untuk file besar, Anda mungkin mencoba mentransfer lebih banyak daripada yang dapat ditangani oleh OS; (c) Anda mengabaikan nilai pengembalian transferFrom, jadi itu mungkin hanya menyalin sebagian file. Inilah sebabnya mengapa org.apache.tools.ant.util.ResourceUtils.copyResource sangat rumit. Perhatikan juga bahwa saat transferFrom tidak masalah, transferTo memecah pada JDK 1.4 di Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick
7
Saya percaya versi terbaru ini membahas masalah-masalah tersebut: gist.github.com/889747
Mark Renouf
11
Kode ini memiliki masalah besar . transferTo () harus dipanggil dalam satu lingkaran. Itu tidak menjamin untuk mentransfer seluruh jumlah yang diminta.
Marquis of Lorne
180

Sekarang dengan Java 7, Anda dapat menggunakan sintaks coba sumber daya berikut:

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

Atau, lebih baik lagi, ini juga dapat dicapai dengan menggunakan kelas File baru yang diperkenalkan di Java 7:

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

Cukup manis, eh?

Scott
sumber
15
Luar biasa Java belum menambahkan hal-hal seperti ini sebelum hari ini. Operasi tertentu hanyalah hal yang mutlak mutlak untuk menulis perangkat lunak komputer. Pengembang Oracle di Jawa dapat belajar satu atau dua hal dari sistem operasi, melihat layanan apa yang mereka sediakan, untuk membuatnya lebih MUDAH bagi pemula untuk bermigrasi.
Rick Hodgin
2
Ah terima kasih! Saya tidak mengetahui kelas "File" baru dengan semua fungsi pembantu. Itu persis apa yang saya butuhkan. Terima kasih untuk contohnya.
ChrisCantrell
1
Dari segi
Pankaj
5
Kode ini memiliki masalah besar . transferTo () harus dipanggil dalam satu lingkaran. Itu tidak menjamin untuk mentransfer seluruh jumlah yang diminta.
Marquis of Lorne
@Scott: Pete meminta solusi satu baris dan Anda sudah sangat dekat ... tidak perlu membungkus Files.copy dalam metode copyFile. Saya baru saja meletakkan Files.copy (Path from, Path to) di awal jawaban Anda dan menyebutkan bahwa Anda dapat menggunakan File.toPath () jika Anda memiliki objek File: Files.copy (fromFile.toPath (), toFile.toPath ())
merampok
89
  • Metode ini adalah rekayasa kinerja (mereka berintegrasi dengan sistem operasi asli I / O).
  • Metode ini bekerja dengan file, direktori, dan tautan.
  • Setiap opsi yang disediakan dapat diabaikan - opsi tersebut opsional.

Kelas utilitas

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

Menyalin direktori atau 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);

Memindahkan direktori atau 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);

Menyalin direktori atau file secara rekursif

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
sumber
Nama paket untuk File salah (harus java.nio.file bukan java.nio). Saya telah mengirimkan hasil edit untuk itu; harap tidak apa-apa!
Stuart Rossiter
43

Di Java 7 itu mudah ...

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

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
Kevin Sadler
sumber
1
Apa yang ditambahkan jawaban Anda ke Scott atau Glen?
Uri Agassi
11
Singkat, lebih sedikit lebih. Jawaban mereka bagus dan terperinci, tetapi saya merindukan mereka ketika memeriksa. Sayangnya ada banyak jawaban untuk ini dan banyak dari mereka panjang, usang dan rumit dan jawaban Scott dan Glen yang baik hilang dalam hal itu (saya akan memberikan upvotes untuk membantu dengan itu). Saya ingin tahu apakah jawaban saya mungkin ditingkatkan dengan menguranginya menjadi tiga baris dengan merobohkan yang ada () dan pesan kesalahan.
Kevin Sadler
Ini tidak berfungsi untuk direktori. Sial, semua orang salah. Lebih banyak masalah komunikasi API yang salah. Saya juga salah.
mmm
2
@momo pertanyaannya adalah bagaimana cara menyalin file.
Kevin Sadler
28

Untuk menyalin file dan menyimpannya ke jalur tujuan Anda, Anda dapat menggunakan metode di bawah ini.

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
sumber
1
Ini akan berhasil, tetapi saya pikir itu tidak lebih baik daripada jawaban lain di sini?
Rup
2
@Rup Jauh lebih baik daripada jawaban lain di sini, (a) karena berfungsi, dan (b) karena tidak bergantung pada perangkat lunak pihak ketiga.
Marquis of Lorne
1
@ EJP OK, tapi tidak terlalu pintar. Penyalinan file harus merupakan operasi OS atau sistem file, bukan operasi aplikasi: Java diharapkan dapat melihat salinan dan mengubahnya menjadi operasi OS kecuali dengan secara eksplisit membaca file ketika Anda menghentikannya melakukan hal itu. Jika Anda tidak berpikir Java dapat melakukannya, apakah Anda percaya untuk mengoptimalkan 1K membaca dan menulis ke dalam blok yang lebih besar? Dan jika sumber dan tujuan berada pada pembagian jarak jauh melalui jaringan lambat maka ini jelas melakukan pekerjaan yang tidak perlu. Ya, beberapa JAR pihak ketiga sangat besar (Jambu!) Tetapi mereka menambahkan banyak hal seperti ini dilakukan dengan benar.
Rup
Bekerja seperti pesona. Solusi terbaik yang tidak membutuhkan perpustakaan pihak ketiga dan berfungsi di java 1.6. Terima kasih.
James Wierzba
@Rup Saya setuju bahwa ini harusnya merupakan fungsi sistem operasi, tetapi saya tidak dapat memahami komentar Anda. Bagian setelah usus besar pertama tidak memiliki kata kerja di suatu tempat; Saya tidak akan 'percaya' untuk tidak berharap Java mengubah blok 1k menjadi sesuatu yang lebih besar, walaupun saya sendiri akan menggunakan blok yang jauh lebih besar; Saya tidak akan pernah menulis aplikasi yang menggunakan file bersama di tempat pertama; dan saya tidak tahu bahwa perpustakaan pihak ketiga mana pun melakukan sesuatu yang lebih 'layak' (apa pun yang Anda maksudkan) daripada kode ini, kecuali mungkin menggunakan buffer yang lebih besar.
Marquis of Lorne
24

Perhatikan bahwa semua mekanisme ini hanya menyalin konten file, bukan metadata seperti izin. Jadi jika Anda menyalin atau memindahkan file .sh yang dapat dieksekusi di linux file baru tidak akan dapat dieksekusi.

Untuk benar-benar menyalin atau memindahkan file, yaitu untuk mendapatkan hasil yang sama dengan menyalin dari baris perintah, Anda benar-benar perlu menggunakan alat asli. Entah skrip shell atau JNI.

Rupanya, ini mungkin diperbaiki di java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Semoga saja!

Brad di Kademi
sumber
23

Pustaka Guava Google juga memiliki metode salin :

salinan statis publik batal ( File  dari,
                         File  ke)
                 melempar IOException
Salinan semua byte dari satu file ke yang lain.

Peringatan: Jika tomewakili file yang ada, file itu akan ditimpa dengan konten from. Jika todan frommerujuk ke file yang sama , konten file itu akan dihapus.

Parameter:from - file sumber to- file tujuan

Melempar: IOException - jika terjadi kesalahan I / O IllegalArgumentException- jikafrom.equals(to)

Andrew McKinlay
sumber
7

Tiga kemungkinan masalah dengan kode di atas:

  1. Jika getChannel melempar pengecualian, Anda mungkin membocorkan aliran terbuka.
  2. Untuk file besar, Anda mungkin mencoba mentransfer lebih banyak daripada yang dapat ditangani oleh OS.
  3. Anda mengabaikan nilai pengembalian transferFrom, jadi itu mungkin hanya menyalin sebagian file.

Inilah mengapa org.apache.tools.ant.util.ResourceUtils.copyResourcesangat rumit. Perhatikan juga bahwa saat transferFrom tidak masalah, transferTo rusak pada JDK 1.4 di Linux (lihat Bug ID: 5056395 ) - Jesse Glick Jan

disajikan
sumber
7

Jika Anda berada di aplikasi web yang sudah menggunakan Spring dan jika Anda tidak ingin menyertakan Apache Commons IO untuk menyalin file sederhana, Anda dapat menggunakan FileCopyUtils dari kerangka Spring.

Balaji Paulrajan
sumber
7

Berikut adalah tiga cara Anda dapat dengan mudah menyalin file dengan satu baris kode!

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

Jambu biji :

File # copy

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Jaskey
sumber
Yang pertama tidak berfungsi untuk direktori. Sial, semua orang salah. Lebih banyak masalah komunikasi API yang salah. Saya juga salah.
mmm
Yang pertama membutuhkan 3 parameter. Files.copyhanya menggunakan 2 parameter adalah untuk Pathuntuk Stream. Cukup tambahkan parameter StandardCopyOption.COPY_ATTRIBUTESatau StandardCopyOption.REPLACE_EXISTINGuntuk PathkePath
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();
        }
    }
}
pengguna3200607
sumber
Jadi perbedaan dari jawaban yang diterima teratas adalah bahwa Anda mendapatkan transferFrom dalam loop sementara?
Rup
1
Bahkan tidak mengkompilasi, dan panggilan createNewFile () berlebihan dan boros.
Marquis of Lorne
3

Salinan NIO dengan buffer adalah yang tercepat menurut pengujian saya. Lihat kode kerja di bawah ini dari proyek uji saya di 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
sumber
bagus! yang ini lebih cepat daripada streaming java.io standar .. menyalin 10GB hanya dalam 160 detik
aswzen
2

Cepat dan bekerja dengan semua versi Java dan juga 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();
}
pengguna1079877
sumber
1
Tidak semua sistem file mendukung memori yang dipetakan file, dan saya pikir itu relatif mahal untuk file kecil.
Rup
Tidak berfungsi dengan versi Java apa pun sebelum 1.4, dan tidak ada yang menjamin satu tulisan saja cukup.
Marquis of Lorne
1

Sedikit terlambat ke pesta, tetapi di sini adalah perbandingan waktu yang dibutuhkan untuk menyalin file menggunakan berbagai metode menyalin file. Saya memutar melalui metode untuk 10 kali dan mengambil rata-rata. Transfer file menggunakan aliran IO tampaknya menjadi kandidat terburuk:

Perbandingan transfer file menggunakan berbagai metode

Berikut adalah metodenya:

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

Satu-satunya kelemahan yang dapat saya lihat saat menggunakan kelas saluran NIO adalah bahwa saya masih belum bisa menemukan cara untuk menunjukkan kemajuan penyalinan file menengah.

Vinit Shandilya
sumber