Стандартный краткий способ скопировать файл в Java?

421

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

Есть ли лучший способ, который остается в пределах языка Java (то есть не подразумевает выполнение специфичных для ОС команд)? Возможно, в каком-нибудь надежном пакете утилит с открытым исходным кодом, который, по крайней мере, затмит эту базовую реализацию и предоставит однострочное решение?

Питер
источник
5
В Apache Commons FileUtils может быть что-то, в частности, методы 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.

delfuego
источник
1
Очень полезно - есть ли у вас понимание того, когда официальный релиз будет включать эти изменения nio?
Питер
2
Публичный релиз Apache Commons IO все еще на 1.4, grrrrrrr
Питер
14
По состоянию на декабрь 2010 года Apache Commons IO имеет версию 2.0.1, которая обладает функциональностью NIO. Ответ обновлен.
Саймон Никерсон
4
Предупреждение для пользователей Android: это НЕ включено в стандартные API Android
IlDan
18
Если вы используете Java 7 или новее, вы можете использовать Files.copy в соответствии с предложением @GlenBest: stackoverflow.com/a/16600787/44737
rob
278

Я бы избегал использования мега-API, как Apache Commons. Это упрощенная операция, встроенная в JDK в новом пакете NIO. Это было отчасти уже связано с предыдущим ответом, но ключевой метод в API 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();
        }
    }
}

Изучение NIO может быть немного сложнее, так что вы можете просто поверить этому механику, прежде чем уйти и попытаться изучить NIO в одночасье. Из личного опыта очень трудно научиться, если у вас нет такого опыта и вы познакомились с IO через потоки java.io.

Джош
источник
2
Спасибо, полезная информация. Я бы все еще спорил о чем-то вроде Apache Commons, особенно если он использует nio (правильно) снизу; но я согласен, что важно понимать основные принципы.
Питер
1
К сожалению, есть предостережения. Когда я скопировал 1,5-гигабайтный файл в Windows 7, 32-битный, он не смог отобразить файл. Мне пришлось искать другое решение.
Антон К.
15
Три возможные проблемы с приведенным выше кодом: (a) если getChannel выдает исключение, вы можете пропустить открытый поток; (б) для больших файлов вы можете пытаться передать больше, чем может выдержать ОС; (c) вы игнорируете возвращаемое значение TransferFrom, поэтому оно может копировать только часть файла. Вот почему org.apache.tools.ant.util.ResourceUtils.copyResource так сложен. Также обратите внимание, что хотя TransferFrom в порядке, TransferTo
Джесси Глик
7
Я полагаю, что эта обновленная версия решает эти проблемы: gist.github.com/889747
Марк Ренуф,
11
Этот код имеет большую проблему. TransferTo () должен вызываться в цикле. Это не гарантирует перевод всей запрашиваемой суммы.
маркиз Лорн
180

Теперь с Java 7 вы можете использовать следующий синтаксис 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() );
    }
}

Или, что еще лучше, это также может быть достигнуто с помощью нового класса Files, представленного в Java 7:

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

Довольно шикарно, а?

Скотт
источник
15
Удивительно, что Java до сегодняшнего дня не добавляла подобные вещи. Определенные операции являются просто абсолютными основами написания компьютерного программного обеспечения. Разработчики Oracle на Java могли бы кое-что узнать из операционных систем, посмотрев, какие услуги они предоставляют, чтобы упростить миграцию новичков.
Рик Ходжин
2
Ах, спасибо! Я не знал о новом классе «Files» со всеми его вспомогательными функциями. Это именно то, что мне нужно. Спасибо за пример.
ChrisCantrell
1
С точки зрения производительности, Java NIO FileChannel лучше, прочитайте эту статью journaldev.com/861/4-ways-to-copy-file-in-java
Pankaj
5
Этот код имеет большую проблему. TransferTo () должен вызываться в цикле. Это не гарантирует перевод всей запрашиваемой суммы.
маркиз Лорн
@ Скотт: Пит попросил однострочное решение, и вы так близки ... нет необходимости оборачивать Files.copy методом copyFile. Я бы просто поместил Files.copy (Path from, Path to) в начале вашего ответа и упомянул, что вы можете использовать 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
@ Момо вопрос был, как скопировать файл.
Кевин Садлер
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 Это значительно лучше, чем другие ответы здесь, (а), потому что он работает, и (б), потому что он не зависит от стороннего программного обеспечения.
маркиз Лорн
1
@EJP Хорошо, но это не очень умно. Копирование файлов должно быть операцией ОС или файловой системы, а не операцией приложения: надеюсь, Java сможет обнаружить копию и превратить ее в операцию ОС, за исключением явного чтения файла, когда вы прекращаете это делать. Если вы не думаете, что Java может это сделать, поверите ли вы этому, чтобы оптимизировать чтение и запись 1K в большие блоки? И если источник и пункт назначения находились на удаленном общем ресурсе по медленной сети, то это явно делает ненужную работу. Да, некоторые сторонние JAR-файлы глупо большие (Guava!), Но они действительно добавляют много таких вещей, как это сделано правильно.
Rup
Работал как шарм. Лучшее решение, которое не требует сторонних библиотек и работает на Java 1.6. Спасибо.
Джеймс Вежба
@Rup Я согласен, что это должна быть функция операционной системы, но я не могу понять смысл вашего комментария. У части после первого двоеточия отсутствует глагол где-то; Я бы не стал «доверять» и не ожидал, что Java превратит блоки размером 1 КБ во что-то большее, хотя я бы, конечно, сам использовал гораздо большие блоки; Я бы никогда не написал приложение, которое бы сначала использовало общие файлы; и я не знаю, что любая сторонняя библиотека делает что-то более «правильное» (что вы подразумеваете под этим), чем этот код, за исключением, вероятно, использования большего буфера.
Маркиз Лорн
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/ учебник / важно / ю / copy.html

Я не могу поверить, что им потребовалось так много времени, чтобы стандартизировать что-то такое простое и простое, как копирование файлов :(

Райан
источник
10
Нет Path.copyTo; это Files.copy.
Джесси Глик
7

Три возможные проблемы с приведенным выше кодом:

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

Вот почему org.apache.tools.ant.util.ResourceUtils.copyResourceтак сложно. Также обратите внимание, что хотя TransferFrom в порядке, TransferTo ломается на JDK 1.4 в Linux (см. Идентификатор ошибки: 5056395 ) - Джесси Глик Ян

Садзи
источник
7

Если вы находитесь в веб-приложении, которое уже использует Spring, и если вы не хотите включать 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);          
}
Jaskey
источник
Первый не работает для каталогов. Черт, все ошибаются. Больше проблем с интерфейсом 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();
        }
    }
}
user3200607
источник
Таким образом, отличие от принятого сверху ответа состоит в том, что вы получили трансфер из цикла while?
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 .. копирование 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();
}
user1079877
источник
1
Не все файловые системы поддерживают файлы с отображением в памяти, и я думаю, что это относительно дорого для небольших файлов.
Rup
Не работает ни с какой версией Java до 1.4, и нет ничего, что гарантировало бы, что одной записи достаточно.
маркиз Лорн
1

Немного опоздал на вечеринку, но вот сравнение времени, затрачиваемого на копирование файла с использованием различных методов копирования файлов. Я перебрал методы 10 раз и набрал среднее. Передача файлов с использованием потоков ввода-вывода кажется худшим кандидатом:

Сравнение передачи файлов различными способами

Вот методы:

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, это то, что я все еще не могу найти способ показать промежуточный процесс копирования файла.

Винит Шандиля
источник