Стандартний стислий спосіб копіювання файлу на Java?


421

Мене завжди непокоїть, що єдиний спосіб копіювання файлу на Java передбачає відкриття потоків, оголошення буфера, читання в одному файлі, прокручування його і виписування його в інший пара. Мережа всіяна подібними, але все ще трохи різними реалізаціями цього типу рішення.

Чи є кращий спосіб залишатися в межах мови Java (тобто не передбачає виконання певних команд ОС)? Можливо, в якомусь надійному пакеті утиліти з відкритим кодом це принаймні затьмарить цю основну реалізацію та забезпечить однорядне рішення?


5
У FileUtils Apache Commons може бути щось , зокрема, методи copyFile .
інструментарій

22
При використанні Java 7, використовуйте Files.copy замість цього, в відповідно до рекомендацій @GlenBest: stackoverflow.com/a/16600787/44737
віднімають

Відповіді:


274

Як згадується вище інструментарій, Apache Commons IO - це шлях, зокрема FileUtils . copyFile () ; він справляється з усім важким підйомом для вас.

І як постскрипт, зауважте, що останні версії FileUtils (наприклад, випуск 2.0.1) додали використання NIO для копіювання файлів; NIO може значно збільшити продуктивність копіювання файлів , значною мірою тому, що NIO підпрограми відкладають копіювання безпосередньо в ОС / файлову систему, а не обробляти її, читаючи та записуючи байти через шар Java. Тож якщо ви шукаєте продуктивність, можливо, варто перевірити, чи використовуєте ви останню версію FileUtils.


1
Дуже корисно - чи маєте ви зрозуміти, коли в офіційному випуску будуть внесені ці зміни nio?
Пітер

2
Публічний реліз IOche Commons IO ще о 1.4, grrrrrrr
Пітер

14
Станом на грудень 2010 року, AOche Commons IO працює на рівні 2.0.1, яка має функціонал NIO. Відповідь оновлено.
Саймон Нікерсон

4
Попередження для користувачів Android: це НЕ включено до стандартних API для Android
IlDan

18
При використанні Java 7 або більш пізньої версії, ви можете використовувати Files.copy як запропоновано @GlenBest: stackoverflow.com/a/16600787/44737
віднімають

278

Я б уникнув використання таких мега-апі, як apache commons. Це спрощена операція і вона вбудована в JDK в новому пакеті NIO. Це було вже пов'язано з попередньою відповіддю, але ключовим методом у apio NIO є нові функції "transferTo" та "transferFrom".

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

В одній із пов’язаних статей показано чудовий спосіб інтегрувати цю функцію у свій код, використовуючи 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();
        }
    }
}

Навчання НІО може бути дещо складним, тому ви, можливо, захочете просто довіритися цьому механіку перед тим, як відправлятися на роботу та намагатися вивчити НІО за ніч. З особистого досвіду можна дуже важко дізнатися, якщо ви не маєте досвіду, і ви були знайомі з IO через потоки java.io.


2
Дякую, корисна інформація. Я б все одно заперечував за щось на зразок Apache Commons, особливо якщо він використовує nio (належним чином) під ним; але я згоден, важливо зрозуміти основні основи.
Пітер

1
На жаль, є застереження. Коли я скопіював 1,5 Gb файл у Windows 7, 32 біт, не вдалося зіставити файл. Довелося шукати інше рішення.
Антон К.

15
Три можливі проблеми з наведеним вище кодом: (a) якщо getChannel кидає виняток, ви можете просочити відкритий потік; (b) для великих файлів ви, можливо, намагаєтесь перенести більше одразу, ніж може працювати ОС; (c) ви ігноруєте повернене значення transferFrom, тому це може бути копіювання лише частини файлу. Ось чому org.apache.tools.ant.util.ResourceUtils.copyResource настільки складний. Також зауважте, що при передачі звідки все в порядку, TransferTo працює
Джессі Глік

7
Я вважаю, що ця оновлена ​​версія вирішує ці проблеми: gist.github.com/889747
Марк

11
Цей код має головну проблему. transferTo () повинен викликатися в циклі. Це не гарантує перерахування всієї запитуваної суми.
Маркіз Лорн

180

Тепер з Java 7 ви можете використовувати наступний синтаксис спробувати-ресурсу:

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

Або ще краще, це також можна досягти за допомогою нового класу Files, представленого в Java 7:

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

Досить запаморочливий, так?


15
Дивовижно, що Java ще сьогодні не додала подібних речей. Певні операції - це лише абсолютні основи написання комп'ютерного програмного забезпечення. Розробники Java Oracle могли дізнатися щось або дві з операційних систем, переглянувши, які послуги вони надають, щоб зробити це ЛЕГШЕ для новачків, що мігрують на них.
Рік Ходгін

2
Ах, дякую! Мені не було відомо про новий клас "Файли" з усіма його допоміжними функціями. У ньому є саме те, що мені потрібно. Дякую за приклад.
ChrisCantrell

1
продуктивність розумна, java NIO FileChannel краще, читайте цю статтю journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj

5
Цей код має головну проблему. transferTo () повинен викликатися в циклі. Це не гарантує перерахування всієї запитуваної суми.
Маркіз Лорн

@Scott: Піт попросив однорядне рішення, і ти так близько ... зайве загортати Files.copy методом copyFile. Я б просто поставив файл Files.copy (Шлях від, Шлях до) на початку вашої відповіді і зазначив, що ви можете використовувати File.toPath (), якщо у вас є існуючі об'єкти File: Files.copy (fromFile.toPath (), toFile.toPath ())
грабувати

89
  • Ці методи розроблені на основі продуктивності (вони інтегруються з нативним введенням-виведенням операційної системи).
  • Ці методи працюють з файлами, каталогами та посиланнями.
  • Кожен із наданих варіантів може бути залишений - вони необов’язкові.

Клас корисності

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

Копіювання каталогу чи файлу

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

Переміщення каталогу або файлу

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

Копіювання каталогу чи файлів рекурсивно

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

Ім'я пакета для файлів було неправильним (має бути java.nio.file, а не java.nio). Я подав редагування для цього; сподіваюся, що це нормально!
Стюарт Россітер

43

У Java 7 це легко ...

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

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

1
Що ваша відповідь додає Скотту чи Глену?
Урі Агассі

11
Це стисло, менше - більше. Їх відповіді хороші та детальні, але я їх пропустив, переглядаючи. На жаль, відповідей на це дуже багато, і багато з них довгі, застарілі та складні, і в цьому губляться гарні відповіді Скотта та Глена (я дам нагороди, щоб допомогти у цьому). Цікаво, чи можу відповідь покращити, зменшивши її до трьох рядків, вибивши існує () та повідомлення про помилку.
Кевін Садлер

Це не працює для каталогів. Чорт забирає, що це все не так. Більше комунікацій API вирішує вашу помилку. Я теж неправильно зрозумів.
ммм

2
@momo питання полягало в тому, як скопіювати файл.
Кевін Садлер

28

Щоб скопіювати файл і зберегти його до пункту призначення, ви можете скористатися наведеним нижче способом.

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
Це спрацює, але я не думаю, що це краще, ніж інші відповіді тут?
Rup

2
@Rup Це набагато краще, ніж інші відповіді тут, (а) тому, що він працює, і (b) тому, що він не покладається на програмне забезпечення сторонніх виробників.
Маркіз Лорн

1
@EJP Добре, але це не дуже розумно. Копіювання файлів має бути операційною системою ОС або файловою системою, а не операцією з додатком: Java, сподіваємось, зможе помітити копію та перетворити її в операцію з ОС, за винятком явного читання файлу, коли ви зупиняєте це. Якщо ви не думаєте, що Java може це зробити, ви довіритесь їй оптимізувати 1К читання та записування у більші блоки? І якщо джерело та місце призначення були на віддаленій радіостанції через повільну мережу, то це, очевидно, робить непотрібну роботу. Так, деякі сторонні JAR є тупо великими (Гуава!), Але вони додають багато речей, як це зроблено належним чином.
Rup

Працював як шарм. Найкраще рішення, яке не потребує сторонніх бібліотек та працює на java 1.6. Дякую.
Джеймс Wierzba

@Rup Я погоджуюся, що це повинна бути функція операційної системи, але я не можу мати жодного іншого сенсу вашого коментаря. Частина після першої двокрапки десь не має дієслова; Я б ні "довіряв" не очікував, що Java перетворить 1k блоки в щось більше, хоча я, звичайно, використовую набагато більші блоки; Я б ніколи не писав програми, яка використовувала в першу чергу спільні файли; і я не знаю, що будь-яка стороння бібліотека робить щось більш "правильне" (що б ви не мали на увазі під цим), ніж цей код, за винятком, ймовірно, використання більшого буфера.
Маркіз Лорнський

24

Зауважте, що всі ці механізми копіюють лише вміст файлу, а не метадані, такі як дозволи. Тож якби ви скопіювали або перемістили виконуваний .sh-файл у Linux, новий файл не виконується.

Для того, щоб справді скопіювати або перемістити файл, тобто отримати такий же результат, як і копіювання з командного рядка, вам фактично потрібно скористатися нативним інструментом. Або сценарій оболонки, або JNI.

Мабуть, це може бути виправлено в java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html . Схрещені пальці!


23

У бібліотеці Google Guava також є метод копіювання :

публічна статична недійсна копія ( файл  із,
                         файл  у)
                 кидає IOException
Копіює всі байти з одного файлу в інший.

Попередження: Якщо toпредставляє наявний файл, цей файл буде перезаписаний вмістом from. Якщо toі fromпосилатися на той самий файл, вміст цього файлу буде видалено.

Параметри:from - вихідний файл to- файл призначення

Кидає: IOException - якщо виникає помилка вводу / виводу IllegalArgumentException- якщоfrom.equals(to)


19

Доступно як стандарт у Java 7, path.copyTo: http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/ навчальний посібник / істотний / io / copy.html

Я не можу повірити, що їм потрібно було так багато часу, щоб стандартизувати щось таке поширене і просте, як копіювання файлів:


10
Немає Path.copyTo; це Files.copy.
Джессі Глік

7

Три можливі проблеми з вищевказаним кодом:

  1. Якщо getChannel кидає виняток, ви можете просочити відкритий потік.
  2. Для великих файлів, можливо, ви намагаєтесь перенести більше відразу, ніж може працювати з ОС.
  3. Ви ігноруєте повернене значення transferFrom, тому це може бути копіювання лише частини файлу.

Ось чому org.apache.tools.ant.util.ResourceUtils.copyResourceце так складно. Також зауважте, що під час передачіFrom в порядку, TransferTo працює на JDK 1.4 в Linux (див. Ідентифікатор помилки: 5056395 ) - Jesse Glick Jan


7

Якщо ви перебуваєте у веб-програмі, яка вже використовує Spring, і якщо ви не хочете включати IO Apache Commons IO для простого копіювання файлів, ви можете використовувати FileCopyUtils рамки Spring.


7

Ось три способи, за допомогою яких ви можете легко копіювати файли з одного рядка коду!

Java7 :

java.nio.file.Files # копія

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

Гуава :

Файли # копія

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}

Перший не працює для каталогів. Чорт забирає, що це все не так. Більше комунікацій API вирішує вашу помилку. Я теж неправильно зрозумів.
ммм

Спочатку потрібно 3 параметри. Files.copyвикористовуючи тільки два параметри для Pathдо Stream. Просто додайте параметр StandardCopyOption.COPY_ATTRIBUTESабо StandardCopyOption.REPLACE_EXISTINGдля 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();
        }
    }
}

Тож відмінність від верхньої прийнятої відповіді полягає в тому, що ви отримали transferFrom через певний час?
Rup

1
Навіть не компілюється, а виклик createNewFile () є зайвим і марним.
Маркіз Лорн

3

Копія NIO з буфером є найшвидшою за моїм тестом. Дивіться робочий код нижче з тестового проекту шахти на веб- сайті 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();
        }    
    }
}

}


приємно! цей швидкий, а не стандартний java.io stream .. копіювання 10 Гб лише за 160 секунд
aswzen

2

Швидка робота з усіма версіями Java та 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
Не всі файлові системи підтримують файли, відображені в пам'яті, і я думаю, що невеликі файли відносно дорогі.
Rup

Не працює з жодною версією Java до 1.4, і немає нічого, що гарантує, що одного запису достатньо.
Маркіз Лорн

1

Трохи запізнюємось на вечірку, але ось порівняння часу, необхідного для копіювання файлу, використовуючи різні способи копіювання файлів. Я переглянув методи протягом 10 разів і взяв середнє значення. Передача файлів за допомогою потоків IO є найгіршим кандидатом:

Порівняння передачі файлів за допомогою різних методів

Ось методи:

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

Єдиний недолік, який я бачу під час використання класу каналів NIO - це те, що я все ще не можу знайти спосіб показати проміжний хід копіювання файлів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.