Standardowy zwięzły sposób na skopiowanie pliku w Javie?

421

Zawsze niepokoiło mnie, że jedynym sposobem na skopiowanie pliku w Javie jest otwarcie strumieni, zadeklarowanie bufora, odczytanie jednego pliku, zapętlenie go i zapisanie go w innej parze. Sieć jest zaśmiecona podobnymi, ale wciąż nieco różnymi implementacjami tego typu rozwiązania.

Czy istnieje lepszy sposób, który mieści się w granicach języka Java (co oznacza, że ​​nie wymaga wykonywania poleceń specyficznych dla systemu operacyjnego)? Być może w jakimś niezawodnym pakiecie narzędziowym typu open source, który przynajmniej zasłoniłby tę podstawową implementację i zapewniłby rozwiązanie jednowierszowe?

Piotr
źródło
5
W pliku Apache Commons FileUtils może być coś, w szczególności metody copyFile .
zestaw narzędzi
22
Jeśli używasz Java 7, zamiast tego użyj Files.copy, zgodnie z zaleceniami @GlenBest: stackoverflow.com/a/16600787/44737
rob

Odpowiedzi:

274

Jak wspomniano powyżej, zestaw narzędzi Apache Commons IO jest najlepszym rozwiązaniem, w szczególności FileUtils . copyFile () ; poradzi sobie z każdym ciężkim podnoszeniem.

I jako postscript zauważ, że najnowsze wersje FileUtils (takie jak wydanie 2.0.1) dodały użycie NIO do kopiowania plików; NIO może znacznie zwiększyć wydajność kopiowania plików , w dużej mierze, ponieważ procedury NIO odraczają kopiowanie bezpośrednio do systemu operacyjnego / plików zamiast obsługiwać je poprzez odczytywanie i zapisywanie bajtów przez warstwę Java. Więc jeśli szukasz wydajności, warto sprawdzić, czy korzystasz z najnowszej wersji FileUtils.

delfuego
źródło
1
Bardzo pomocne - czy masz jakiś wgląd w to, kiedy oficjalne wydanie wprowadzi te zmiany nio?
Peter
2
Publiczne wydanie Apache Commons IO wciąż o 1.4, grrrrrrr
Peter
14
W grudniu 2010 r. Apache Commons IO ma wersję 2.0.1, która ma funkcję NIO. Odpowiedź zaktualizowana.
Simon Nickerson
4
Ostrzeżenie dla osób z Androidem: NIE jest ono zawarte w standardowych interfejsach API Androida
IlDan
18
Jeśli używasz Java 7 lub nowszej wersji, możesz użyć Files.copy zgodnie z sugestią @GlenBest: stackoverflow.com/a/16600787/44737
rob
278

Unikałbym używania mega api, takich jak apache commons. Jest to operacja uproszczona i jest wbudowana w JDK w nowym pakiecie NIO. W pewnym sensie było to już związane z poprzednią odpowiedzią, ale kluczową metodą w interfejsie NIO są nowe funkcje „transferTo” i „transferFrom”.

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

Jeden z powiązanych artykułów pokazuje świetny sposób na zintegrowanie tej funkcji z Twoim kodem za pomocą metody 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();
        }
    }
}

Nauka NIO może być trochę trudna, więc możesz zaufać tej mechanice, zanim pójdziesz i spróbujesz nauczyć się NIO przez noc. Z własnego doświadczenia bardzo trudno jest się nauczyć, jeśli nie masz doświadczenia i zostałeś wprowadzony do IO za pośrednictwem strumieni java.io.

Josh
źródło
2
Dzięki, przydatne informacje. Nadal będę argumentować za czymś takim jak Apache Commons, szczególnie jeśli używa nio (poprawnie) pod spodem; ale zgadzam się, że ważne jest zrozumienie podstawowych zasad.
Peter
1
Niestety istnieją zastrzeżenia. Kiedy skopiowałem plik 1,5 Gb na Windows 7, 32-bit, nie udało się zmapować pliku. Musiałem szukać innego rozwiązania.
Anton K.
15
Trzy możliwe problemy z powyższym kodem: (a) jeśli getChannel zgłosi wyjątek, możesz wyciec otwarty strumień; (b) w przypadku dużych plików możesz próbować przesłać więcej plików naraz niż system operacyjny może obsłużyć; (c) ignorujesz wartość zwracaną przez transferFrom, więc może to być kopiowanie tylko części pliku. Dlatego org.apache.tools.ant.util.ResourceUtils.copyResource jest tak skomplikowany. Zauważ również, że chociaż transferFrom jest w porządku, transferTo psuje się w JDK 1.4 w systemie Linux: bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick
7
Uważam, że ta zaktualizowana wersja rozwiązuje te problemy: gist.github.com/889747
Mark Renouf
11
Ten kod ma poważny problem. Metoda transferTo () musi być wywoływana w pętli. Nie gwarantuje to przeniesienia całej żądanej kwoty.
Markiz Lorne
180

Teraz w Javie 7 możesz użyć następującej składni try-with-resource:

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

Lub jeszcze lepiej, można to również osiągnąć za pomocą nowej klasy Files wprowadzonej w Javie 7:

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

Dość sprytny, co?

Scott
źródło
15
To niesamowite, że Java nie dodała dziś takich rzeczy. Niektóre operacje są absolutnie niezbędne do pisania oprogramowania komputerowego. Programiści Java firmy Oracle mogli dowiedzieć się czegoś więcej na temat systemów operacyjnych, patrząc na oferowane przez nich usługi, aby ułatwić migrację początkującym użytkownikom.
Rick Hodgin,
2
Ach dzięki! Nie byłem świadomy nowej klasy „Files” ze wszystkimi jej funkcjami pomocniczymi. Ma dokładnie to, czego potrzebuję. Dzięki za przykład.
ChrisCantrell,
1
pod względem wydajności, java NIO FileChannel jest lepszy, przeczytaj ten artykuł journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj
5
Ten kod ma poważny problem. Metoda transferTo () musi być wywoływana w pętli. Nie gwarantuje to przeniesienia całej żądanej kwoty.
Markiz Lorne
@Scott: Pete poprosił o rozwiązanie jednowierszowe i jesteś tak blisko ... nie jest konieczne pakowanie plików.copy w metodę copyFile. Po prostu umieściłem Files.copy (Ścieżka od, Ścieżka do) na początku twojej odpowiedzi i wspomnę, że możesz użyć File.toPath (), jeśli masz istniejące obiekty File: Files.copy (fromFile.toPath (), toFile.toPath ())
rob
89
  • Metody te są opracowywane pod kątem wydajności (integrują się z natywnymi operacjami wejścia / wyjścia systemu operacyjnego).
  • Te metody działają z plikami, katalogami i łączami.
  • Każdą z dostarczonych opcji można pominąć - są one opcjonalne.

Klasa użyteczności

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

Kopiowanie katalogu lub pliku

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

Przenoszenie katalogu lub pliku

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

Kopiowanie katalogu lub pliku rekurencyjnie

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
źródło
Nazwa pakietu dla plików była nieprawidłowa (powinna brzmieć java.nio.file, a nie java.nio). Przesłałem do tego edycję; mam nadzieję, że to w porządku!
Stuart Rossiter,
43

W Javie 7 jest to łatwe ...

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

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
Kevin Sadler
źródło
1
Co twoja odpowiedź dodaje do Scotta lub Glen'a?
Uri Agassi
11
Jest zwięzły, mniej znaczy więcej. Odpowiedzi są dobre i szczegółowe, ale przeoczyłem je. Niestety jest wiele odpowiedzi na to pytanie, a wiele z nich jest długich, przestarzałych i skomplikowanych, a dobre odpowiedzi Scotta i Glena zgubiły się w tym (oddam głos za pomoc w tym). Zastanawiam się, czy moją odpowiedź można poprawić, zmniejszając ją do trzech wierszy, znokautując komunikat o istnieniu () i błędzie.
Kevin Sadler
To nie działa w przypadku katalogów. Cholera, wszyscy źle to rozumieją. Więcej problemów z komunikacją interfejsu API powoduje błąd. Ja też źle to zrozumiałem.
mmm
2
@momo pytanie brzmiało, jak skopiować plik.
Kevin Sadler
28

Aby skopiować plik i zapisać go na docelowej ścieżce, możesz użyć poniższej metody.

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
źródło
1
To zadziała, ale nie sądzę, że jest lepsze niż inne odpowiedzi tutaj?
Rup
2
@Rup Jest znacznie lepszy niż inne odpowiedzi tutaj, (a) ponieważ działa i (b) ponieważ nie opiera się na oprogramowaniu innych firm.
Markiz Lorne
1
@EJP OK, ale to nie jest zbyt mądre. Kopiowanie plików powinno być operacją systemu operacyjnego lub systemu plików, a nie operacją aplikacji: mam nadzieję, że Java wykryje kopię i zmieni ją w operację systemu operacyjnego, z wyjątkiem jawnego odczytu pliku, w którym go zatrzymujesz. Jeśli nie uważasz, że Java może to zrobić, czy zaufałbyś temu, że zoptymalizuje odczyty 1K i zapisuje w większych blokach? A jeśli źródło i miejsce docelowe znajdowały się w zdalnym udziale w wolnej sieci, to wyraźnie robi to niepotrzebną pracę. Tak, niektóre pliki JAR innych firm są głupio duże (Guava!), Ale dodają wiele takich rzeczy, zrobionych poprawnie.
Rup,
Działa jak urok. Najlepsze rozwiązanie, które nie wymaga bibliotek stron trzecich i działa na Javie 1.6. Dzięki.
James Wierzba
@Rup Zgadzam się, że powinna to być funkcja systemu operacyjnego, ale nie mogę zrozumieć twojego komentarza. W części po pierwszym dwukropku gdzieś brakuje czasownika; Nie spodziewałbym się, że Java nie zamieni bloków 1k w coś większego, chociaż z pewnością sam użyłbym znacznie większych bloków; Nigdy nie napisałbym aplikacji, która używałaby współdzielonych plików; i nie wiem, czy jakakolwiek biblioteka strony trzeciej robi coś bardziej „poprawnego” (cokolwiek przez to rozumiesz) niż ten kod, z wyjątkiem prawdopodobnie użycia większego bufora.
Markiz Lorne
24

Zauważ, że wszystkie te mechanizmy kopiują tylko zawartość pliku, a nie metadane, takie jak uprawnienia. Więc jeśli miałbyś skopiować lub przenieść plik wykonywalny .sh na Linuksie, nowy plik nie byłby wykonywalny.

Aby naprawdę skopiować lub przenieść plik, tj. Aby uzyskać taki sam wynik jak kopiowanie z wiersza poleceń, w rzeczywistości musisz użyć rodzimego narzędzia. Albo skrypt powłoki, albo JNI.

Najwyraźniej można to naprawić w java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Skrzyżowane palce!

Brad w Kademi
źródło
23

Biblioteka Google Guava ma również metodę kopiowania :

publiczna statyczna nieważna kopia ( Plik  z,
                         Plik  do)
                 zgłasza wyjątek IOException
Kopiuje wszystkie bajty z jednego pliku do drugiego.

Ostrzeżenie: jeśli toreprezentuje istniejący plik, plik ten zostanie zastąpiony zawartością from. Jeśli toi fromodnoszą się do tego samego pliku, zawartość tego pliku zostanie usunięta.

Parametry:from - plik źródłowy to- plik docelowy

Zgłasza: IOException - jeśli wystąpi błąd we / wy IllegalArgumentException- jeślifrom.equals(to)

Andrew McKinlay
źródło
7

Trzy możliwe problemy z powyższym kodem:

  1. Jeśli getChannel zgłosi wyjątek, możesz wyciec otwarty strumień.
  2. W przypadku dużych plików możesz próbować przesłać więcej plików naraz niż system operacyjny może obsłużyć.
  3. Ignorujesz wartość zwracaną przez transferFrom, więc może to być kopiowanie tylko części pliku.

To dlatego org.apache.tools.ant.util.ResourceUtils.copyResourcejest takie skomplikowane. Zauważ też, że chociaż transferFrom jest w porządku, transferTo psuje się w JDK 1.4 w Linuksie (patrz Bug ID: 5056395 ) - Jesse Glick Jan

saji
źródło
7

Jeśli korzystasz z aplikacji internetowej, która już korzysta z Spring i nie chcesz dołączać Apache Commons IO do prostego kopiowania plików, możesz użyć FileCopyUtils z Spring.

Balaji Paulrajan
źródło
7

Oto trzy sposoby łatwego kopiowania plików za pomocą jednego wiersza kodu!

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

Guawa :

Pliki # kopiuj

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Jaskey
źródło
Pierwszy nie działa w przypadku katalogów. Cholera, wszyscy źle to rozumieją. Więcej problemów z komunikacją interfejsu API powoduje błąd. Ja też źle to zrozumiałem.
mmm
Pierwszy wymaga 3 parametrów. Files.copyużycie tylko 2 parametrów jest Pathdo Stream. Wystarczy dodać parametr StandardCopyOption.COPY_ATTRIBUTESlub StandardCopyOption.REPLACE_EXISTINGdo PathceluPath
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();
        }
    }
}
użytkownik3200607
źródło
Więc różnica w stosunku do najczęściej akceptowanej odpowiedzi polega na tym, że masz transfer z pętli while?
Rup
1
Nawet się nie kompiluje, a wywołanie metody createNewFile () jest zbędne i marnotrawione.
Markiz Lorne
3

Według mojego testu kopia NIO z buforem jest najszybsza. Zobacz działający kod poniżej z mojego projektu testowego na 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
źródło
miły! ten jest szybki, a nie standardowy strumień java.io. kopiowanie 10 GB tylko w 160 sekund
aswzen
2

Szybko i współpracuj ze wszystkimi wersjami Java, także 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();
}
użytkownik1079877
źródło
1
Jednak nie wszystkie systemy plików obsługują pliki mapowane w pamięci i myślę, że jest to stosunkowo kosztowne w przypadku małych plików.
Rup
Nie działa z żadną wersją Javy wcześniejszą niż 1.4 i nic nie gwarantuje, że pojedynczy zapis jest wystarczający.
Markiz Lorne
1

Trochę za późno na imprezę, ale tutaj jest porównanie czasu kopiowania pliku przy użyciu różnych metod kopiowania plików. Zapętlałem metody 10 razy i brałem średnią. Najgorszym kandydatem wydaje się transfer plików przy użyciu strumieni IO:

Porównanie przesyłania plików przy użyciu różnych metod

Oto metody:

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

Jedyną wadą, jaką widzę podczas korzystania z klasy kanału NIO, jest to, że nadal nie mogę znaleźć sposobu na pokazanie postępu kopiowania plików pośrednich.

Vinit Shandilya
źródło