Pytanie w sprawie file, copy, java – Standardowy zwięzły sposób kopiowania pliku w Javie?

413

Zawsze przeszkadzało mi, że jedynym sposobem kopiowania pliku w Javie jest otwieranie strumieni, deklarowanie bufora, odczytywanie w jednym pliku, zapętlanie go i zapisywanie go w drugiej parze. Sieć jest zaśmiecona podobnymi, ale wciąż nieco innymi implementacjami tego typu rozwiązania.

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

Jeśli używasz Java 7, użyj zamiast tego Files.copy, zgodnie z zaleceniem @GlenBest:stackoverflow.com/a/16600787/44737 rob
W Apache Commons może być cośFileUtils, W szczególnościskopiować plik metody. toolkit

Twoja odpowiedź

16   odpowiedzi
271

Jak wspomniano powyżej, zestaw narzędzi Apache Commons IO jest właściwym rozwiązaniemFileUtils.skopiować plik(); poradzi sobie z całym ciężkim liftingiem.

Jako postscript należy zauważyć, ż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 dlatego, że procedury NIO odraczają kopiowanie bezpośrednio do systemu operacyjnego / systemu plików zamiast obsługiwać go poprzez odczyt i zapisywanie bajtów przez warstwę Java. Jeśli więc szukasz wydajności, warto sprawdzić, czy używasz najnowszej wersji FileUtils.

Jest to również bardzo szybkie. Zastąpiłem fragment kodu, który wykonywaliśmy, xCopy, aby skopiować niektóre katalogi i bardzo dobrze zwiększył prędkość. (Użyłem wersji repozytorium) drye
Nie mam pojęcia ... Przeszukałem wszystkie źródła informacji publicznej, które mogę znaleźć, i nie ma żadnej wzmianki o możliwej dacie wydania. Strona JIRA pokazuje tylko pięć otwartych problemów, więc może niedługo? delfuego
Bardzo pomocne - czy masz wgląd w to, kiedy oficjalne wydanie będzie zawierać te zmiany nio? Peter
Ostrzeżenie dla użytkowników Androida: NIE jest to zawarte w standardowych interfejsach API systemu Android IlDan
276

Unikałbym używania mega api jak apache commons. Jest to uproszczona operacja i wbudowana w JDK w nowym pakiecie NIO. Było to już powiązane z poprzednią odpowiedzią, ale kluczową metodą w NIO api 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 połączonych artykułów pokazuje świetny sposób na zintegrowanie tej funkcji z Twoim kodem za pomocą transferu:

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ę skomplikowana, więc możesz po prostu zaufać temu mechanikowi, zanim wyruszysz i spróbujesz nauczyć się NIO przez noc. Z osobistego doświadczenia może być bardzo trudno się nauczyć, jeśli nie masz doświadczenia i zostałeś wprowadzony do IO za pośrednictwem strumieni java.io.

@EJP Już nie rozwijam się w Javie. Możesz mieć rację, więc możesz zaproponować właściwy sposób. Josh
W systemie Windows Server stwierdzam, że pliki kopiowane w ten sposób z NIO sporadycznie nie mogą być zmieniane ani usuwane, nawet po wywołaniu close () na wszystkim. Uważam, że jest to spowodowane mapowaniem pamięci, które NIO tworzy, wymagając wyrzucania śmieci, jak opisano wten post. foz
Ten kod mapoważny problem. TransferTo () musi być wywołany w pętli. Nie gwarantuje przeniesienia całej żądanej kwoty. user207421
Dzięki, przydatne informacje. Nadal kłóciłbym się o coś takiego jak Apache Commons, zwłaszcza jeśli pod nim nio (poprawnie); zgadzam się jednak, że ważne jest zrozumienie podstawowych zasad. Peter
179

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

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

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

Dość snazzy, co?

@Scott: Pete poprosił o rozwiązanie jednokreskowe i jesteś tak blisko ... nie trzeba zawijać Files.copy w metodzie copyFile. Po prostu umieściłem Files.copy (Ścieżka z, Ścieżka do) na początku odpowiedzi i wspomnę, że możesz użyć File.toPath (), jeśli masz istniejące obiekty File: Files.copy (fromFile.toPath (), toFile.toPath ()) rob
Ten kod mapoważny problem. TransferTo () musi być wywołany w pętli. Nie gwarantuje przeniesienia całej żądanej kwoty. user207421
Ah dzięki! Nie wiedziałem o nowej klasie „Pliki” ze wszystkimi jej funkcjami pomocniczymi. Ma dokładnie to, czego potrzebuję. Dzięki za przykład. ChrisCantrell
wydajność mądry, java NIO FileChannel jest lepszy, przeczytaj ten artykułjournaldev.com/861/4-ways-to-copy-file-in-java Pankaj
To niesamowite, że Java nie dodała takich rzeczy do dziś. Niektóre operacje są absolutnymi podstawami pisania oprogramowania komputerowego. Twórcy oprogramowania Java firmy Oracle mogli nauczyć się czegoś z systemów operacyjnych, sprawdzając, jakie usługi oferują, aby ułatwić nowicjuszom migrację. Rick Hodgin
36

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);
Jest zwięzły, mniej znaczy więcej. Ich odpowiedzi są dobre i szczegółowe, ale brakowało mi ich podczas przeglądania. Niestety, istnieje wiele odpowiedzi na to pytanie, a wiele z nich jest długich, przestarzałych i skomplikowanych, a dobre odpowiedzi Scotta i Glen zagubiły się w tym (będę udzielał pomocy w tym zakresie). Zastanawiam się, czy moja odpowiedź mogłaby zostać poprawiona przez zmniejszenie jej do trzech linii przez wybicie komunikatu istniejącego () i błędu. Kevin Sadler
Co twoja odpowiedź dodaje do Scotta lub Glen'a? Uri Agassi
To nie działa dla katalogów. Niech to szlag trafi wszystkich. Więcej komunikacji za pomocą interfejsu API powoduje Twoją winę. Ja też się myliłem. momomo
@momo pytanie brzmiało, jak skopiować plik. Kevin Sadler
2

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

Dostępny w standardzie w Javie 7, path.copyTo:http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/tutorial/essential/io/copy.html

Nie mogę uwierzyć, że tak długo zajęło im standaryzowanie czegoś tak powszechnego i prostego jak kopiowanie plików :(

Nie ma Path.copyTo; to jest Files.copy. Jesse Glick
Twój link to 404 człowiek samuel owino
28

Aby skopiować plik i zapisać go na ścieżce docelowej, 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();
    }
}
Działa z Java 6, bez dodatkowych słoików. John Henckel
Ten kod jest nieprawidłowy.InputStream.read może zwrócić 0, nawet jeśli jest więcej danych.InputStream.read zwraca -1, gdy nie ma więcej danych. Zatem pętla powinna się zakończyć, gdy zwraca -1, a nie 0. Solomonoff's Secret
Pracował jak urok. Najlepsze rozwiązanie, które nie wymaga bibliotek zewnętrznych i działa na Javie 1.6. Dzięki. James Wierzba
To zadziała, ale nie sądzę, żeby było lepiej niż inne odpowiedzi tutaj? Rup
7

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

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();
        }
    }
}
Więc różnica w porównaniu z najlepszą akceptowaną odpowiedzią jest taka, że ​​masz transfer w pętli? Rup
Nawet się nie kompiluje, a wywołanie createNewFile () jest zbędne i marnotrawne. user207421
86
Metody te są zaprojektowane z myślą o wydajności (integrują się z natywnymi we / wy systemu operacyjnego).Te metody działają z plikami, katalogami i łączami.Każda z dostarczonych opcji może zostać pominięta - są opcjonalne.Klasa użytkowa
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 );
Nazwa pakietu dla plików była nieprawidłowa (powinna być java.nio.file not java.nio). Wysłałem do tego edycję; mam nadzieję, że to dobrze! Stuart Rossiter
22

Biblioteka Google Guava ma równieżmetoda kopiowania:

public static void copy(File from,
                        File to)
                 throws IOException
Kopiuje wszystkie bajty z jednego pliku do drugiego.

Ostrzeżenie: Jeślito reprezentuje istniejący plik, plik zostanie zastąpiony zawartościąfrom. Jeślito ifrom odnoszą się dopodobnie plik, zawartość tego pliku zostanie usunięta.

Parametry:from - plik źródłowyto - plik docelowy

Rzuca: <a href="http://download.oracle.com/javase/6/docs/api/java/io/IOException.html?is-external=true" rel="nofollow noreferrer" title="class or interface in java.io">IOException</a> - jeśli wystąpi błąd we / wy<a href="http://download.oracle.com/javase/6/docs/api/java/lang/IllegalArgumentException.html?is-external=true" rel="nofollow noreferrer" title="class or interface in java.lang">IllegalArgumentException</a> - Jeślifrom.equals(to)

7

Trzy możliwe problemy z powyższym kodem:

Jeśli getChannel zgłasza wyjątek, możesz przeciekać otwarty strumień.W przypadku dużych plików możesz próbować przenieść więcej na raz, niż system operacyjny może obsłużyć.Ignorujesz wartość zwracaną przez transferFrom, więc może to być kopiowanie tylko części pliku.

Dlategoorg.apache.tools.ant.util.ResourceUtils.copyResource jest tak skomplikowane. Zauważ również, że podczas transferu z OK, prześlij do JDK 1.4 w systemie Linux (patrzIdentyfikator błędu: 5056395) - Jesse Glick Jan

24

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

Aby naprawdę skopiować lub przenieść plik, tj. Aby uzyskać ten sam rezultat, co kopiowanie z wiersza poleceń, musisz użyć natywnego narzędzia. Skrypt powłoki lub 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!

3

Kopia NIO z buforem jest najszybsza zgodnie z moim testem. Zobacz poniższy kod roboczy z mojego projektu testowego pod adresemhttps://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();
        }    
    }
}

}

ładny! ten jest szybki, a nie standardowy java.io strumień .. kopiowanie 10GB tylko w 160 sekund aswzen
7

Oto trzy sposoby łatwego kopiowania plików za pomocą jednej linii 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 # kopia

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}
Pierwszy wymaga 3 parametrów.Files.copy używa się tylko 2 parametrówPath doStream. Po prostu dodaj parametrStandardCopyOption.COPY_ATTRIBUTES lubStandardCopyOption.REPLACE_EXISTING dlaPath doPath Pimp Trizkit
Pierwszy nie działa dla katalogów. Niech to szlag trafi wszystkich. Więcej komunikacji za pomocą interfejsu API powoduje Twoją winę. Ja też się myliłem. momomo
1

Trochę późno na imprezę, ale oto porównanie czasu potrzebnego na skopiowanie pliku przy użyciu różnych metod kopiowania plików. Zapętlałem metody 10 razy i wziąłem średnią. Przesyłanie plików za pomocą strumieni IO wydaje się być najgorszym kandydatem:

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

Jedynym mankamentem, jaki widzę podczas korzystania z klasy kanałów NIO, jest to, że wciąż nie mogę znaleźć sposobu na pokazanie postępu pośredniego kopiowania plików.

Powiązane pytania