Стандартний стислий спосіб копіювання файлу на 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.